Search code examples
swiftforced-unwrapping

How is a return value of AnyObject! different from AnyObject


The NSMetadataItem class in the Cocoa framework under Swift contains the following function:

func valueForAttribute(key: String!) -> AnyObject!

I'm still learning the difference (and details) between forced unwrapping and optional chaining. In the above function, does this mean:

  1. The key parameter must have a value, and

  2. The return value is guaranteed to have a value?

My primary concern is with the exclamation point following the return value - once I have assigned the return value:

var isDownloadedVal = item.valueForAttribute(NSMetadataUbiquitousItemIsDownloadedKey)

Do I need to include an if let block when examining it, or am I guaranteed that it will have a value I can examine safely?


Solution

  • TLDR: Treat Foo! as if it were Foo.

    Many Cocoa calls include implicitly unwrapped optionals, and their need for it is very likely the reason the feature even exists. Here's how I recommend thinking about it.

    First, let's think about a simpler case that doesn't involve AnyObject. I think UIDevice makes a good example.

    class func currentDevice() -> UIDevice!
    

    What's going on here? Well, there is always a currentDevice. If this returned nil that would indicate some kind of deep error in the system. So if we were building this interface in Swift, this would likely just return UIDevice and be done with it. But we need to bridge to Objective-C, which returns UIDevice*. Now that should never be nil, but it syntactically could be nil. Now in ObjC, we typically ignore that fact and don't nil-check here (particularly because nil-messaging is typically safe).

    So how would we express this situation in Swift? Well, technically it's an Optional<UIDevice>, and you'd wind up with:

    class func currentDevice() -> UIDevice?
    

    And you'd need to explicitly unwrap that every time you used it (ideally with an if let block). That would very quickly drive you insane, and for nothing. currentDevice() always returns a value. The Optional is an artifact of bridging to ObjC.

    So they invented a hack to work around that (and I think it really is a hack; I can't imagine building this feature if ObjC weren't in the mix). That hack says, yes, it's an Optional, but you can pretend it's not, and we promise it's always going to be a value.

    And that's !. For this kind of stuff, you basically ignore the ! and pretend that it's handing you back a UIDevice and roll along. If they lied to you and return nil, well, that's going to crash. They shouldn't have lied to you.

    This suggests a rule: don't use ! unless you really need to (and you pretty much only need to when bridging to ObjC).

    In your specific example, this works in both directions:

    func valueForAttribute(key: String!) -> AnyObject!
    

    Technically it takes an Optional<String>, but only because it's bridged to NSString*. You must pass non-nil here. And it technically returns you Optional<AnyObject>, but only because it's bridged to id. It promises that it won't be nil.