Swift 1.2

Swift, true to its name, is moving fast. This week marks the beta release of Swift 1.2, a major update to the language. The Swift team has responded to so many of the community’s requests in one fell swoop, we’re overflowing with new and exciting features. Every line-item in this announcement is a big advancement: incremental builds, improved error messages and stability in Xcode, static class properties, support for C unions and bitfields, bridging of Swift enums to Objective-C, safer type casting, improvements to single-line closures, and more.

In what follows, let’s look at two major aspects of the release that will significantly improve the experience of working in Swift: first, big changes in if let optional binding (“finally”), and second, new access to nullability annotations in Objective-C.

Improved Optional Binding

Swift 1.2 allows multiple simultaneous optional bindings, providing an escape from the trap of needing deeply nested if let statements to unwrap multiple values. Multiple optional bindings are separated by commas and can be paired with a where clause that acts like the expression in a traditional if statement. As such, the byzantine pyramid of doom has been renovated into a mid-century modern ranch:

Old:

let a = "10".toInt()
let b = "5".toInt()
let c = "3".toInt()

if let a = a {
    if let b = b {
        if let c = c {
            if c != 0 {
                println("(a + b) / c = \((a + b) / c)")
            }
        }
    }
}

New:

if let a = a, b = b, c = c where c != 0 {
    println("(a + b) / c = \((a + b) / c)")     // (a + b) / c = 5
}

The order of execution in these two examples is identical. Using the new syntax, each binding is evaluated in turn, stopping if any of the attempted bindings is nil. Only after all the optional bindings are successful is the where clause checked.

An if statement can actually have more than one let binding separated by commas. Since each let binding can bind multiple optionals and include a where clause, some truly sophisticated logic is possible with this construct. (Thanks to Stephen Celis for helping clarify this point.)

What’s more, later binding expressions can reference earlier bindings. This means you can delve into Dictionary instances or cast an AnyObject? value to a specific type, then use it in another expression, all in a single if let statement.

To revisit the canonical example, this is what parsing a big block of JSON can look like in Swift 1.2. The example uses one if let block to handle the optionals that come with using NSBundle, NSURL and NSData, then another to map several AnyObject? instances from the interpreted JSON to specific types:

var users: [User] = []

// load and parse the JSON into an array
if let
    path     = NSBundle.mainBundle().pathForResource("users", ofType: "json"),
    url      = NSURL(fileURLWithPath: path),
    data     = NSData(contentsOfURL: url),
    userList = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as? [[String: AnyObject]]
{
    // extract individual users
    for userDict in userList {
        if let
            id      = userDict["id"] as? Int,
            name    = userDict["name"] as? String,
            email   = userDict["email"] as? String,
            address = userDict["address"] as? [String: AnyObject]
        {
            users.append(User(id: id, name: name, ...))
        }
    }
}
[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "[email protected]",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "[email protected]",
    "address": {
      "street": "Victor Plains",
      "suite": "Suite 879",
      "city": "Wisokyburgh",
      "zipcode": "90566-7771",
      "geo": {
        "lat": "-43.9509",
        "lng": "-34.4618"
      }
    },
    "phone": "010-692-6593 x09125",
    "website": "anastasia.net",
    "company": {
      "name": "Deckow-Crist",
      "catchPhrase": "Proactive didactic contingency",
      "bs": "synergize scalable supply-chains"
    }
  },
  ...
]

I see many commas in our future.

Nullability Annotations

When Swift was first released, every call to a Cocoa API method took and returned implicitly unwrapped optionals (i.e., AnyObject!). Because they crash a program on access, implicitly unwrapped return values are inherently unsafe if there isn’t clear documentation of whether or not a method will return a null value. All those exclamation marks were a sign of bad form. Sure, Swift bridged to the Cocoa APIs, but it always looked grumpy about it.

As the beta releases rolled on through the summer and fall, internal audits gradually removed the offending punctuation, replacing implicitly unwrapped optionals with either true optionals or non-optional, never-gonna-be-nil values. This vastly improved the experience of working with Cocoa, but there was no mechanism to mark up third-party code in the same way, leaving part of the problem in place.

But no longer—Swift 1.2 ships alongside a new version of Clang. New property attributes and pointer annotations allow you to indicate whether a pointer, be it an Objective-C property, method parameter, or return value, can or won’t ever be nil.

  • nonnull: Indicates that the pointer should/will never be nil. Pointers annotated with nonnull are imported into Swift as their non-optional base value (i.e., NSData).
  • nullable: Indicates that the pointer can be nil in general practice. Imported into Swift as an optional value (NSURL?).
  • null_unspecified: Continues the current functionality of importing into Swift as an implicitly unwrapped optional, ideally to be used during this annotation process only.
  • null_resettable: Indicates that while a property will always have a value, it can be reset by assigning nil. Properties with a non-nil default value can be annotated this way, like tintColor. Imported into Swift as a (relatively safe) implicitly unwrapped optional. Document accordingly!

The first three annotations can also be used with C pointers and block pointers, using the doubly-underscored __nonnull, __nullable, and __null_unspecified. The last annotation, null_resettable, is only valid as an Objective-C property attribute.

Nullability in Action

As an example to show the benefit of these annotations, let’s take a look at a data controller used to handle a list of locations, each with a possible attached photo:

@interface LocationDataController : NSObject

@property (nonatomic, readonly) NSArray *locations;
@property (nonatomic, readonly) Location *latestLocation;

- (void)addPhoto:(Photo *)photo forLocation:(Location *)location;
- (Photo *)photoForLocation:(Location *)location;
@end

Without any nullability annotations, each pointer in my LocationDataController class is imported to Swift as an implicitly unwrapped optional:

class LocationDataController : NSObject {
    var locations: [AnyObject]! { get }
    var latestLocation: Location! { get }

    func addPhoto(photo: Photo!, forLocation location: Location!)
    func photoForLocation(location: Location!) -> Photo!
}

Enough! With! The shouting! Here’s how I can now annotate the Objective-C interface:

@interface LocationDataController : NSObject

@property (nonnull, nonatomic, readonly) NSArray *locations;
@property (nullable, nonatomic, readonly) Location *latestLocation;

- (void)addPhoto:(nonnull Photo *)photo forLocation:(nonnull Location *)location;
- (nullable Photo *)photoForLocation:(nonnull Location *)location;
@end

First, the properties—my locations list is nonnull, since at worst it will be an empty array, but latestLocation can be nil if there are no locations in the list yet. Likewise, the parameters to my two methods should always have a value, yet because not all locations have a photo, that second method returns a nullable photo. Back in Swift, the results are much better—that is, clearer about how to safely use the data controller and no more grumpy !s:

class LocationDataController : NSObject {
    var locations: [AnyObject] { get }
    var latestLocation: Location? { get }

    func addPhoto(photo: Photo, forLocation location: Location)
    func photoForLocation(location: Location) -> Photo?
}

NS_ASSUME_NONNULL_BEGIN/END

Annotating any pointer in an Objective-C header file causes the compiler to expect annotations for the entire file, bringing on a cascade of warnings. Given that most annotations will be nonnull, a new macro can help streamline the process of annotating existing classes. Simply mark the beginning and end of a section of your header with NS_ASSUME_NONNULL_BEGIN and ..._END, then mark the exceptions.

Another revision of our data controller interface from above results in a more readable version with the exact same Swift profile:

@interface LocationDataController : NSObject
NS_ASSUME_NONNULL_BEGIN

@property (nonatomic, readonly) NSArray *locations;
@property (nullable, nonatomic, readonly) Location *latestLocation;

- (void)addPhoto:(Photo *)photo forLocation:(Location *)location;
- (nullable Photo *)photoForLocation:(Location *)location;

NS_ASSUME_NONNULL_END
@end

Not Just for Swift

The new Objective-C nullability annotations have huge benefits for code on the Swift side of the fence, but there’s a substantial gain here even without writing a line of Swift. Pointers marked as nonnull will now give a hint during autocomplete and yield a warning if sent nil instead of a proper pointer:

// Can I remove a photo by sending nil?
[dataController addPhoto:nil forLocation:currentLocation];
// Nope -- Warning: Null passed to a callee that requires a non-null argument

Excitingly, all that is just half the story. In addition to the changes in Swift syntax and compiler savoir faire, the standard library has also seen a major revision, including a proper Set class (so long, dear friend). Okaaay, so none of our code works anymore, and Stack Overflow has 21,000 out-of-date Swift questions? It’s still fun to be along for the ride.