Rss

Swift and the Last Mile

So the initial thrill of Swift is over, and lots of us are digging in. So far, there haven’t been any screaming denunciations that I’ve heard, but surely they’re coming… if there aren’t, then we’re all too easy to please.

The best thing I can say about Swift is that it’s deeply pragmatic. Rather than set about creating some beautiful, “perfect” language (or identifying one that already exists), Swift’s highest priorities include reconciling C and Objective-C and the entirety of the existing frameworks for iOS and OS X. Having to rework all those frameworks to suit some other language is presumably a non-starter, as Apple’s tried that without success three times now (the Cocoa bridges from Java, Ruby, and Python).

As I rework our iOS intro book for Swift — no, it won’t be formally announced until the publisher is convinced it’s a go, but anyone following me on Twitter knows I’ve been working hard on it for the last week — much of what Swift does for beginners pleases me. To the degree that it unifies the conventions of C and Obj-C under the guise of global functions and methods respectively, it certainly saves me from having to tell beginners “square brace idiom here, but parentheses here, because history.” And Swift’s closure syntax is far more memorable than blocks ever were; I’m already writing them by hand without resorting to the cheat sheet.

But the fact that the frameworks were programmatically adapted for Swift rather than carefully combed over introduces some new pain points.

Consider that most of the methods in the documentation have parameters marked with the ! character. Chances are you won’t notice it until you have to write a callback or a closure that takes parameters of this type. I didn’t until I started dealing with the SLRequestHandler, which is passed to SLRequest‘s performRequestWithHandler() to call a social networking API, passing in a closure to deal with the result. Let’s look at the signature of that SLRequestHandler:


typealias SLRequestHandler = 
     (NSData!, NSHTTPURLResponse!, NSError!) -> Void

Bang! There goes my app!

OK, now that I’m receiving them as parameters to my closure, I suddenly care about the bang characters. So what are they? Implicitly unwrapped optionals. Here’s the idea: if those were genuine Swift optionals (denoted by the ? character), you would have to unwrap each one by converting to a local constant with let and testing against nil. That would, of course, be incredibly burdensome. But they have to be optionals, because sending nil objects around happens all the time in Objective-C, and that’s reflected in the existing frameworks. So what this does is to automatically unwrap the optional when you reference it, without requiring you to assign it to a local constant or variable. However, this does not check against whether the optional is nil, and you’re still exposed to a runtime exception if you, say, assume that the NSError is non-nil and try to get a localizedDescription() from it.

As Apple’s gparker explains on the devforums, it’s a tradeoff between safety and convenience. It lets you skip the nil checking for parameters you “just know” must have a value. But to “know” that in a way that would suit the compiler — maybe some sort of annotations on the existing Objective-C classes — might well require a forensic scan of all the existing frameworks to see if a nil case is ever possible, and that’s a tall order.

Lost in Translation

Objective-C’s fondness for harmlessly passing around nil is expressive in that language, but potentially burdensome in Swift. Some other conventions in C and Obj-C also translate poorly to Swift. Consider the Swift signature to NSJSONSerialization‘s JSONObjectWithData():


class func JSONObjectWithData(_ data: NSData!,
                      options opt: NSJSONReadingOptions,
                        error error: NSErrorPointer) -> AnyObject!

That should look familiar enough if you’ve used this API, right? But if you’ll recall, in Objective-C the second parameter is a bit-field of behavior flags, and the third is an NSError** that is populated upon return if an error is encountered. So how does that work in Swift?

Well, for the options, NSJSONReadingOptions looks like this:


struct NSJSONReadingOptions : RawOptionSet {
    init(_ value: UInt)
    var value: UInt
    static var MutableContainers: NSJSONReadingOptions { get }
    static var MutableLeaves: NSJSONReadingOptions { get }
    static var AllowFragments: NSJSONReadingOptions { get }
}

So for no options, you hit up the initializer with NSJSONReadingOptions(0), but for actually filling the bitfield, it’s not clear how this would work, as the structure provides getters for the three defined behaviors, but the actual value definitions for those behaviors ((1UL << 0), etc.) seems to exist only in Obj-C. The superclass RawOptionSet also seems to not exist in the public documentation.

It looks like this sort of bitfield options type is known to be a problem in Swift, so it could be that this will change before iOS 8 and Yosemite go final.

Erroneous

Then there's the question of the NSError*, which becomes a NSErrorPointer in Swift. One could imagine being passed back a tuple with the parsed JSON object and any error that occurred. Another option would be to use in-out parameters, via the & modifier — this is the approach advocated by the "Adopting Cocoa Design Patterns" chapter of the "Using Swift With Cocoa and Objective-C" guide in Xcode's documentation.

But the programmatic bridge gives us this more direct equivalent, the NSErrorPointer, to replace NSError*. Note also that the error parameter to JSONObjectWithData() is not an optional, so you can't just pass in nil to ignore the error anymore, since actually encountering a JSON parsing error means your app dies with fatal error: Can't unwrap Optional.None.

So you have to send something for that third parameter, but what? How about an optional? No, you can't declare var parseError : NSErrorPointer?, because again the parameter doesn't take an optional.

So after dealing with this on the devforums for about an hour while writing this blog, here's the scoop. You don't actually send a NSErrorPointer at all. You declare a local variable of type NSError? (ie, an optional), and then providing the in-out modifier to the parameter automagically converts it to an NSErrorPointer. So I guess we just have to remember this rule-of-thumb and not explicitly provide the type the documentation calls for… though I suppose we don't have to like it.

The other problem in my case was that deliberately passing in bad JSON data will cause the entire returned JSONObjectWithData object to be nil, so the local variable itself needs to be defined as an optional (in other words, AnyObject?, since it could be either an array or dictionary, and was id in Obj-C).

And I guess that makes sense because the return type is defined as AnyObject! so it is indeed an optional. Specifically, an implicitly unwrapped optional. Just like we saw above.

Anyways, here's the right way to do it:


var parseError : NSError?
let jsonResponse : AnyObject? =
  NSJSONSerialization.JSONObjectWithData(evilData,
    options: NSJSONReadingOptions(0),
    error: &parseError);
println ("json response: (jsonResponse), error (parseError)")

parseError and jsonResponse are both optionals now, so they must be tested against nil to be used.

Better

So, the magical conversion from &NSError? to NSErrorPointer works. But I'll bet you a dollar nobody would have designed it that way from scratch.

And that's fine. That's going to happen at this point. Because Swift is bridging to the old stuff really well 95% of the time or more. If we get cut on the bleeding edge a little bit, that's what we get for going first, right?

Of course, any frameworks designed from here on out will benefit from having Swift in mind as they're designed. Think of iOS frameworks prior to blocks and properties. A modern NSTimer would probably take a block to execute, rather than the target-action paradigm of a single method to call and a "user info" object to supply to it. On the other hand, think of more recent frameworks, and how stuff like AV Foundation and Social are all about using completion handler blocks for long-running actions like media export or webservice calls. These seem natural and easy-to-use to us today.

Newer frameworks will get there. And there may be more work to clean up all the classes that are there now. After all, it's just the first beta. And it's going pretty well so far.

Comments (2)

  1. Whoa! Thanks the the NSError help!

  2. […] that I’ve been even remotely subtle about it, but with today’s release of iOS 8 and the end of the NDA on its SDK, I can now […]

Leave a Reply

Your email address will not be published. Required fields are marked *