Friday, January 2, 2015

Swift and Cocoa

The primary reasons for Apple's language gurus going to Swift as the core language for building iOS and Mac OS X applications was to make it easier for developers to create good, secure, stable applications.  It wasn't actually a conspiracy to drive us all mad, it was actually a long-game move that was very well executed.  Yes, its not "there" yet in many ways.  But here's why you should use it anyway, and a couple of things to look out for: optionals and type-safety.

Objective-C has a lot of pointers flying around, it is very easy to be loosey-goosey about when those pointers are null.  Especially since its OK to send a message to a null object, and because the clang compiler ensures initialisation in many cases, we often write sloppy code with respect to null pointers.  Apps that crash are not only a bad experience for users of iOS products, but they're also likely to have security holes.  Also apps that are light on type safety can similarly be unstable and have security problems.

Also, there's type safety.  You'd think being C-based that Objective-C was type safe, but the existence of the id type, a generic go-anywhere do-anything type based on the venerable C void * pointer type, means that a lot of the time that type safety is circumvented.  An Objective-C array for example is of type NSArray and can have any Objective-C object stored into it, as its members are all id types.

Swift does a lot for type safety, and while it does not do away with null pointers, what it does do is force you to be explicit about them.  This is done with the Optional type.  An Optional behaves like a union struct in C where you can either have a None value, or a Some<T> value. (Actually its implemented with a Swift enum).  Swifts generics have a syntactic sugar for this where instead of writing

var userAnswer : Optional<String>

you write

var userAnswer : String?

which means a var that may or may not contain a value.  In general in Swift you are encouraged to initialise variables as you declare them, and then you get the benefit of type inference like this:

var userAnswer = "Placeholder"

This is perfectly type safe: the variable userAnswer is strongly typed as String, which is inferred by the compiler from the initialisation, and the variable cannot have a value of another type assigned to it.

Some folks seeing code like this have assumed that Swift is something like Javascript where the variables are dynamically typed: they can contain data of different types at different times. Nope.  Swift is not a scripting language.  Its not even a managed language in the true sense of that term.  Its a compiled language that has a powerful and expressive syntax, and a rich standard library, but its not a scripting language.

OK, so what does all this fancy footwork with Optionals actually buy you?  For my money its like trading one problem for another that is easier to solve.  I like to write invariants in to my comments in code:
"Such and such a condition will always hold here."  
By putting such things into the class definition you remind yourself that you are in control: there is not some spooky stuff going on.

Superstitious coding is a terrible disease.  If you hear someone saying "I don't know how I fixed it but I had to type that to get it to work" or "I know that variable should always have a value, but what if someone sets it to null?" then you know superstition and magical beliefs have taken hold in your team and its time to call the exorcist.  In other words abandon all hope and compile your code with a ouija board because you don't control it any more.

Instead, use invariants.  Yes comments are not code, but you can type them in nonetheless and at least enforce them during debug builds using asserts.  This is coding by contract and if you start out as you mean to continue then your code will make sense as it continues to be modified long into the future.

This is where Optionals come in.  They allow you to make contracts and invariants succinctly and naturally as you write code.  If you use a Swift implicitly unwrapped optional by writing:

var userAnswer : String!

func displayTheAnswer()
    myAlert(title: "Got it!", text: "You said \(userAnswer)")

...then what you are doing is effectively writing the following invariants and asserts:

/* Invariant: I undertake and swear & declare that I will make sure this has a value by the time its used! */
var userAnswer : String!

func displayTheAnswer()
    /* kaboom if I didn't live up to my promise */
    assert(userAnswer != nil, "You failed to stick to the invariant!")
    myAlert(title: "Got it!", text: "You said \(userAnswer)")

And while this can still crash at run-time if the assertion is wrong, its much much easier to read what you are intending, and to locate the cause of the crash at the site of the error, not at some point much later where the result of the null finally propagates to.

In fact when you write UI code in Swift such as the following:

@IBOutlet var surnameLabel : UILabel!

you are using an Optional, and an implicitly unwrapped one at that.  This is what you get if you use Interface Builder to control drag a control into your Swift NSViewController.

Because you know that you controller will spend most of its life-time running with the surnameLabel set to the value obtained from the NIB file, this is a sound invariant promise to make.  Superstitious coding where you run around checking to see if this is null all the time makes no sense.

So I like the idea.  Its a bit weird at first, but its a good language feature, and once the idiomatic use settles down will make for more stable and more secure software.

Where things are a little rough is at the interface between Swift and Apple's now long-in-the-tooth User Interface library, Cocoa.  Cocoa is full of crufty, bizarre, unsafe and down-right confusing APIs that no matter what modern language you used would always behave badly.

Here's an example:

Here I'm trying to implement an NSValueTransformer to use in the Swift project I'm working on that uses Cocoa and bindings.  The class method transformedValueClass (where class means "+" instead of "-" or instance method) returns a Class as a result.  Because Swift's Array type is actually implemented as a Swift Struct it does not conform to the AnyObject protocol; and since AnyClass - the return type from this function - is actually a type alias for AnyObject.Type - you cannot return the type "array of strings" from this function.

Instead to work around you need to return an NSArray - the old Objective-C type - which will get transparently bridged to a Swift array, but which of course is not type safe.

Other issues are the difficulties working with key technologies like Key-Value Coding, which is critical to the Cocoa bindings used by Interface Builder, and the cryptic type messages that can come out of the type system at the interface with Objective-C.

Its here at the interface between Cocoa and Swift that the new language really runs into rough terrain.  Still, its worth persevering with, and the online community is abuzz with new pundits who are happy to drop knowledge bombs on your gnarliest conundrums, providing you've put some time into scratching your head over them yourself.

Also, my prediction is that while at present the Cocoa layer for Swift is mostly just ultra-thin bridging-headers and wrappers, we'll slowly see more pure Swift classes put in front of these things and kludgy Cocoa issues will slowly be solved.

At present it is perfectly do-able to write your new apps in Swift and I think starting out greenfield projects using Objective-C is probably a mistake, all things considered.

What do you think?

No comments:

Post a Comment