magiclantern
magiclantern

Reputation: 798

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

Upvotes: 1

Views: 2894

Answers (2)

Rob
Rob

Reputation: 438277

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)
    }
}

Upvotes: 1

oisdk
oisdk

Reputation: 10091

Your line:

files["textFile.rtf"]?.regularFileContents

Returns a doubly-wrapped optional. One for the dictionary look up, one for the .regularFileContents. When you did the if let, you unwrapped it once: that's why your error was:

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

But then, on your second version, you just check if rtfData is nil - you don't unwrap it:

if (rtfData != nil)

Which gives you the doubly-wrapped optional.

The easiest way to get around it is to use flatMap:

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

flatMap takes an optional, and unwraps it to put it into a function that returns an optional. If either the original optional, or the value returned from the function, evaluates to nil, the whole thing evaluates to nil. But the value it returns is only singly wrapped.

What flatMap is doing is basically:

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

This code would work as well. Both methods safely unwrap rtfData twice.

Upvotes: 1

Related Questions