Search code examples
swiftcocoansdataoption-typensfilewrapper

Can’t seem to avoid force unwrapping in Swift


Update: this is regarding a Mac app created with the Document-based Application template in Xcode, and I’m overriding

override func readFromFileWrapper(fileWrapper: NSFileWrapper, ofType typeName: String, error outError: NSErrorPointer) -> Bool

I’m trying to read in a file from an NSFileWrapper and it seems like I can't get away from having at least one ! in there.

First, I tried

if let rtfData = files["textFile.rtf"]?.regularFileContents,
        newString = NSMutableAttributedString(data: rtfData, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil) {
                text = newString
                return true
        }

So I get this error

Value of optional type 'NSData?' not unwrapped; did you mean to use '!' or '?'?

And I have to either cast regularFileContents as NSData! or I have to force unwrap it in the NSMutableAttributedString's initializer (data: rtfData!).


So then I tried

let rtfData = files["textFile.rtf"]?.regularFileContents

if (rtfData != nil) {
    if let newString = NSMutableAttributedString(data: rtfData, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil) {
        text = newString
        return true
    }
}

Which results in

Cannot find an initializer for type 'NSMutableAttributedString' that accepts an argument list of type '(data: NSData??, options: [String : String], documentAttributes: nil, error: nil)'"

So I have to change the initializer to say data: rtfData!!, which I'm not even sure what that means.

Or, I can cast regularFileContents as NSData?, and then I can only use one ! in the initializer.

Update: Here's another thing I’ve tried since posting. I figured the double ? in NSData?? could be due to me optionally unwrapping the NSFileWrapper (files["textFile.rtf"]?) so I tried this:

if let rtfWrapper = files["textFile.rtf"],
    rtfData = rtfWrapper.regularFileContents,
    newString = NSMutableAttributedString(data: rtfData, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil) {
        text = newString
        return true
    }

The compiler still wants me to force unwrap NSData.


I am pretty thoroughly confused.

What is NSData??, why is it “doubly” optional, why is it the result of .regularFileContents, and how can I safely, without !-ing all the way, get the NSData and pass it to the NSMutableAttributedString’s intializer?


Another update

I figured maybe the files constant could be the source of another optional wrapping:

let files = fileWrapper.fileWrappers

But the compiler won't let me if let it. If I try, for example,

if let files = fileWrapper.fileWrappers {
    if let rtfFile = files["textFile.rtf"] {
        if let rtfData = rtfFile.regularFileContents {
            if let newString = NSMutableAttributedString(data: rtfData, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil) {
                text = newString
                return true
            }
        }
    }
}

The compiler gives this error:

Bound value in a conditional binding must be of Optional type

About fileWrapper.fileWrappers

The fileWrapper variable is a parameter of the method, which is

override func readFromFileWrapper(fileWrapper: NSFileWrapper, ofType typeName: String, error outError: NSErrorPointer) -> Bool

Solution

  • You can use optional binding for the file first, and then proceed from there:

    if let file = files["textFile.rtf"],
        contents = file.regularFileContents,
        newString = NSMutableAttributedString(data: contents, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil) {
            text = newString
            return true
    }
    

    If you're using fileWrappers dictionary (a [NSObject : AnyObject]), you have to do some additional casting. This example is using a file I happen to have in that file wrapper, but hopefully it illustrates the idea:

    var text: NSString!
    
    func someMethod(fileWrapper: NSFileWrapper) {
        let files = fileWrapper.fileWrappers
    
        if let file = files["test.json"] as? NSFileWrapper,
            contents = file.regularFileContents,
            newString = NSString(data: contents, encoding: NSUTF8StringEncoding)  {
                text = newString
                println(text)
        }
    }