RxSwift DelegateProxy with required methods

Once you’ve sipped from the Reactive Kool-Aid it can become emotionally painful to have to implement callback methods the old-fashioned way when you hit a delegate-style API that doesn’t have a prebuilt Observable wrapper.

Max Alexander did a great tutorial post on how to implement your own DelegateProxy for such an API, but unfortunately it won’t work for delegates that that have required methods. A common mistake is to implement the required delegate method in your DelegateProxy class, forward the  call using self._forwardToDelegate, and then call rx_delegate.observe() as before. This will result in the fairly misleading message:

Delegate proxy is already implementing `xxx:`, a more performant way of registering might exist.

The right way to implement a DelegateProxy for a delegate protocol with required methods involves doing the following (using the Google Identity Toolkit here as an example):

  1. First implement the DelegateProxy as per Max’s instructions, i.e.:
    class RxGIDSignInDelegateProxy: DelegateProxy, GIDSignInDelegate, DelegateProxyType {
    
        static func currentDelegateFor(object: AnyObject) -> AnyObject? {
            let signin = object as! GIDSignIn
            return signin.delegate
        }
    
        static func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
            let signin = object as! GIDSignIn
            signin.delegate = delegate as? GIDSignInDelegate
        }
    }
    
  2. Next declare a 1ocal PublishSubject property in your proxy, and publish Next & Error events to the subject within the delegate method. You can see we’re also forwarding to the traditional delegate (if one exists), and for completeness’ sake, sending Completed on deinit.
        let signInSubject = PublishSubject<GIDGoogleUser>()
    
        func signIn(signIn: GIDSignIn!, didSignInForUser user: GIDGoogleUser!, withError error: NSError!) {
            if let u = user {
                signInSubject.on(.Next(u))
            } else if let e = error {
                signInSubject.on(.Error(e))
            }
            self._forwardToDelegate?.signIn(signIn, didSignInForUser: user, withError: error)
        }
    
        deinit {
            signInSubject.on(.Completed)
        }
    
  3. At this point, we can implement our rx_* extension method on the relevant class. PublishSubject is a subclass of Observable, so we can just return this value directly.
    extension GIDSignIn {
        public var rx_delegate: DelegateProxy {
            return proxyForObject(RxGIDSignInDelegateProxy.self, self)
        }
    
        public var rx_userDidSignIn: Observable<GIDGoogleUser> {
            let proxy = proxyForObject(RxGIDSignInDelegateProxy.self, self)
            return proxy.signInSubject
        }
        
        // implement your optional delegate methods with `rx_delegate.observe`
    }
    

That’s it! Observe away without sullying your code with delegate callbacks.

Advertisements

Swift ErrorTypes – A Modest Proposal

After the WWDC announcement, when I got over my initial knee-jerk ‘Exceptions – ick!’ reaction, I came around to being a supporter of the Swift 2 error handling model. While it would be nice to be able to desugar the implicit result type, and there’s a glaringly obvious hole with async error handling, for general failable synchronous calls it works well. However, there’s one aspect that really bothers me. Consider the following code:

enum MathError: ErrorType {
    case DivideByZeroError
}

func divide(l: Int, _ r: Int) throws -> Int {
    guard r != 0 else { throw MathError.DivideByZeroError }
    return l / r
}

func calculate() {
    do {
        let result = try divide(10, 2)
        print("Result = \(result)")
    } catch MathError.DivideByZeroError {
        print("Can’t divide by zero!")
    } catch {
        // this should never happen
        break
    }
}

Errors thrown by a function are completely untyped, so Swift is unable to infer that the first catch clause is exhaustive – this means you ALWAYS have to include a catch-all at the end of your do block if you want to completely handle the error. This isn’t just a case of redundant code – if you add an error case later on, you’re going to suddenly (and silently) hit your ‘This should never happen’ handler, which is probably not what you’d expect (and seems out of step with the way switch statements work).

The other problem is that we have no idea from the function signature what errors we should be trying to catch. Apple introduced a Throws: keyword in the doc comments, but relying on the original developer to keep comments up to date can be… ineffective.

A number of people have proposed that throwing functions should be declared with a list of all the types they can throw, rather like Java’s checked exceptions. This will nicely solve our divide function example:

func divide(l: Int, _ r: Int) throws MathError -> Int {
    guard r != 0 else { throw MathError.DivideByZeroError }
    return l / r
}

func calculate() {
    do {
        let result = try divide(10, 2)
        print("Result = \(result)")
    } catch MathError.DivideByZeroError { // this catch is exhaustive
        print("Can’t divide by zero!")
    }
}

Which is great – we now have a sensible exhaustive catch statement, we’ll get helpful compile errors if we add a MathError case and forget to handle it, and the function definition is clearer and self-documenting. However, this is a fairly simple case, and the problems with checked exceptions were always much more evident with larger & more modular codebases – for instance, we’d start to see functions like:

func openDatabase(path: String) throws libuvError, URLFormatError, ZipArchiveError, SQLiteError -> Database {

Technically in this case we’d still be able to write an exhaustive catch without using a catch-all, but I can give a rock-solid guarantee that no-one ever will. A function signature like this leaks internal implementation details, and the exposed error types are very unlikely to be useful to API consumers.

Additionally, this can have an invasive impact on the rest of the code – consider the scenario where these error types are annotated on all functions that call openDatabase, then all the functions that call those functions, and so-on up the line until we have a monster catch (all) block. Now imagine we update our database code and add a new ErrorType, or change the zip library and that ErrorType is now different. We could end up having to touch a dozen files to cover a semantically unimportant change. It’s also worth noting that the Swift team have explicitly said they don’t want “pedantic lists of possible error types, like Java”.

Now this is a somewhat pathological case (although I have seen a lot of Java code written like this in the past!) and hopefully ‘good developers’ wouldn’t write code this way. The way this problem should be solved would be either:

  1. Erase the typed error information that’s not useful to consumers and just throw an untyped ErrorType – this brings us back full-circle – or
  2. Translate the internal errors into a meaningful new ErrorType, such as:
enum DatabaseError: ErrorType {
    case InvalidPath(message: String)
    case CorruptDatabase(message: String)
}

Ultimately, the more I’ve thought about this, the less useful a list of multiple ErrorTypes seems to be. However, I think there’s still value in being able to (optionally) annotate a single custom ErrorType, as in the case of the MathError or a consolidated DatabaseError from our openDatabase call. So my Modest Proposal is stated thus:

// allow us to do this:
func myFunction() throws -> Int

// or this:
func myFunction() throws CustomError -> Int

// but not this:
func myFunction() throws CustomErrorOne, CustomErrorTwo -> Int

A single custom ErrorType is much closer to the underlying language model, neatly complements the Swift 2.1 function covariance, delivers most of the benefits of typed errors, and limits the damage that slovenly error propagation can do to the codebase.

Using Xcode Playgrounds for Presentations

Playground Presentation

Playgrounds are primarily designed as a learning tool, but with Xcode 7’s multiple pages and expanded rich markup support, they’re also really great for presentations – they allow you to mix slide-like text & image content with interactive code (for coding demos). Because everyone loves coding demos!

I used this technique last week at /dev/world/2015, and thought I’d share a few tips if you’re interested in doing your own playground presentations.

  1. Don’t try to put too much content on the one page; otherwise you’ll spend half the presentation scrolling up & down.
  2. Add a key binding to the ‘Show Rendered Markup’ menu option – you’ll be using it a lot!
  3. Use the image markup (//: ![[Alt Text]](image_name.jpg)) to show images from your ‘Resources’ folder – e.g. diagrams you’d normally put on a slide.
  4. Don’t use a dark text theme if you’ll be presenting on a projector – a light theme will be more readable. Also, don’t forget to change the Console font size in your presentation theme!
  5. Use custom Xcode snippets rather than typing in code – it’s much quicker & less error-prone, but your audience will still feel like the code is coming together in front of them. Another option is to pre-fill most of the code and just add the interesting bits live.
  6. If you’re using a Swift file in ‘Sources’ to hide supporting code, note that types must be public to be visible to the playground.
  7. Swift code in ‘Sources’ can’t link to custom frameworks, so you’ll need to put your supporting code in a separate framework if you want to do this. ‘Sources’ is best used for basic helper functions.
  8. Implementing CustomPlaygroundQuickLookable on your types can add a lot of fun to your presentation if it’s something that lends itself to a visual.

Lastly, be prepared for the playground to crash (because it will!)

My /dev/world talk is available online if you’d like to see the playground presentation in action.

Watch App Development Blog – Week 6

 I’m blogging my progress in developing an Apple Watch App. Read the previous instalments here.

Telstra – grrr!

I had a frustrating week trying to track down an odd issue – I wouldn’t get any train times load when I was walking around (i.e. near the station, where I want to test), but there was nothing showing up in the logs. The app would work perfectly at home when plugged into Xcode. This put a bit of a dampener on the planned Pebble app field testing. Eventually I managed to log an error message (NSURLErrorDomain -1003 – hostname not found). It turns out the tutum.io subdomain assigned to my docker endpoint isn’t resolving on any of Telstra’s DNS servers. I’m running Google DNS at home, which resolves the hostname fine, and so doesn’t exhibit the issue, but on 4G I’m at the mercy of the Telstra DNS. After an hour on the phone with Telstra, this has been ‘escalated to Level 3 support’ and I’ll get a response ‘within 7 days’. I’ve switched back to an IP address so I can continue my testing.

What did we learn here?

While I’m obviously shocked that Telstra’s internet services could be anything less than spectacular, the key takeaway is that leaving logging out of your code DOESN’T SAVE TIME. Swift makes it trivially easy to return a typesafe, idiot-proof error result that forces you to think critically about how you’re managed error conditions in your code. Consider this snippet (the culprit):

func get<T: JSONConvertible>(path: String, f: [T] ->()) {
    Alamofire.request(.GET, hostname + path)
        .responseJSON { (_, _, json, _) in
        if let json = json as? [NSDictionary] {
            f(json.map({ T(dictionary: $0) }))
        } else {
            f([])
        }
    }
}

func getAllStations(f: [Station] -> ()) {
    get("/train", f)
}
  1. I’m ignoring the error object that Alamofire is helpfully returning (the last parameter in responseJSON)
  2. I’m returning an empty array if literally anything goes wrong. Networking error, HTTP error (like a 500 from the server), malformed JSON payload, the lot = empty array.

In this case the empty station array was overwriting the locally cached stations, and my ‘nearestStation’ method was never producing a result. Because it’s just refreshing a local cache of fairly static data, I can safely ignore errors if I already have data. This could be done by testing the array count in the callback, but we should be able to do better.

Let’s change our ApiClient get method to the following:

enum Result<T> {
    case Value(Box<T>)
    case Error(NSError)
}

func get<T: JSONConvertible>(path: String, f: Result<[T]> ->()) {
    Alamofire.request(.GET, hostname + path)
        .responseJSON { (_, _, json, error) in
        if let json = json as? [NSDictionary] {
            f(Result.Value(Box(json.map({ T(dictionary: $0) }))))
        } else if let error = error {
            f(Result.Error(error))
        } else {
            f(Result.Error(NSError(domain: ApiClientErrorDomain,
                code: 1,
                userInfo: [NSLocalizedDescriptionKey: "An unknown error occurred."])))
        }
    }
}

We’re defining a Result type that returns either the requested value, or an NSError. Ignore the Box, this is only a figment of your imagination, and is totally not a hack to work around the Swift compiler’s problems with ‘non-fixed multi-payload enum layouts’. Note I’m also creating an ‘unknown error’ to handle the case where I don’t get back an error object from Alamofire. Just in case.

I use the updated API as follows:

        getAllStations { r in
            switch r {
            case let .Value(s):
                setStations(s.unbox)
            case let .Error(e):
                println("Error refreshing stations from the server: \(e.localizedDescription)")
            }
        }

So we have glorious logging in case of error and a rather annoying ‘unbox’ call. Importantly though, due to the signature of the API, it’s now much harder to lazily ignore error handling.

Watch App Development Blog – Week 4 *cough* 5

 I’m blogging my progress in developing an Apple Watch App. Read the previous instalments here.

So, um, I missed a week. Sorry about that. Sad face.

Step 4: The Pebble

We don’t have access to any Apple Watch hardware yet, so it’s difficult to get a good feel for how you will use your app in context. I was given a Pebble for Christmas though, and thought it may be worthwhile getting the core information displayed on a Pebble app so I can experience and analyse the usage flow behind a watch app with this data.

Pebble: the Palm Pilot of Smartwatches

First, my impressions of the pebble:

  • Battery life is not too bad, I get about a week of normal usage. It doesn’t compare favourably to several years of battery life on a traditional watch (like my trusty Tag 2000), but it should soundly spank most newer-generation smart watches, including the Apple Watch, based on the rumours to date.
  • The screen is awful if you’re used to a high-resolution smartphone display. The B&W 144 x 168 display looks like 90s tech.
  • The Watch itself is plasticky (lasted a whole day before it copped a small but visible scratch on the face), and too large and ungainly for most wrists.
  • The app/watchface marketplace is fairly limited & most of the apps have a ‘hobbyist’ feel to them. I don’t necessarily mean that in a negative manner – it’s great to see the enthusiasm, but without a good mechanism to monetise apps there’s not the same level of investment & innovation that I see in the iOS App Store or the Play Store.
  • The hardware is fairly slow and anaemic.
  • Development for the pebble is difficult. The Pebble C API is very low level, requires a lot of careful memory management, can only run on the device, and can’t be debugged. They’ve released a JavaScript API to try to make the experience a bit better, though I haven’t tried it.
  • If you’re using your pebble with an iPhone, the experience is less than seamless due to Apple’s bluetooth accessory restrictions. Some functionality  (e.g. network access) stops working if the Pebble iOS app has been terminated from the background. Third party iOS apps have a single Pebble connection to share, and communication can only be initiated from the phone.

To me, the product is very reminiscent of the early Palm Pilots – clunky B&W screen, an awkward developer experience, small hobbyist developer community etc. The future’s yet to be written, but the Pebble will need to undergo radical and ruthless improvement to keep pace with the latest smartwatches.

The Pebble App

The general concept behind the watch app is a simple display showing the departure times for the closest station. This should provide the ‘in context’ component of the most important watch app functionality. The initial UI design (pictured) includes the nearest station, and the destination, pattern, and minutes remaining for the next four departures from the station.

Pebble Mockup

The simplest way to manage communication between the phone app & the watch app is the AppSync API. The general semantics of the API involve syncing a dictionary of shared data between the phone & watch; it also makes data storage on the watch more convenient. The downside is that this requires a specific key for each individual data element synced to the watch – i.e. specific numbered departures rather than a variable array of scheduled trains.

With that in mind, the keys were defined thus:

#define KEY_STATION 0
#define KEY_DEST_1 1
#define KEY_TIME_1 2
#define KEY_DEST_2 3
#define KEY_TIME_2 4
#define KEY_DEST_3 5
#define KEY_TIME_3 6
#define KEY_DEST_4 7
#define KEY_TIME_4 8

The main issue I ran into with AppSync was a storage limitation. The sample code includes a 30 byte sync buffer which is insufficient for most data sync requirements, but it wasn’t immediately obvious that’s what the error DICT_NOT_ENOUGH_STORAGE was referring to. Upping the buffer to 128 bytes solved the issue. That should be enough for anybody.

Once the data was synced, I update the UI using the following function:

static void drawText() {
  const Tuple *tuple;
  if ((tuple = app_sync_get(&s_sync, KEY_STATION))) {
    if (tuple->value->cstring[0] == 0) {
      text_layer_set_text(station_text_layer, "Waiting for data");
    } else {
      text_layer_set_text(station_text_layer, tuple->value->cstring);
    }
  }
  for (int i = 0; i < 4; i++) {
    if ((tuple = app_sync_get(&s_sync, i * 2 + 1))) {
      if (tuple->value->cstring[0] == 0) {
        text_layer_set_text(dest_text_layers[i], "");
      } else {
        text_layer_set_text(dest_text_layers[i], tuple->value->cstring);
      }
    }
    if ((tuple = app_sync_get(&s_sync, i * 2 + 2))) {
      if (!tuple->value->int32) {
        text_layer_set_text(time_text_layers[i], "");
      } else {
        time_t departure_time = tuple->value->int32;
        time_t current_time = time(NULL);
        int minutes = (departure_time - current_time)/60;
        char time_str[5];
        snprintf(time_str, 5, "%dm", minutes);
        text_layer_set_text(time_text_layers[i], time_str);
      }
    }
  }
}

…where dest_text_layers and time_text_layers are four element arrays containing references to the text layers on the watch UI.

Can you spot the bug? If you haven’t done much work with embedded systems it’s not obvious. Critically, the documentation for text_layer_set_text says:

The string is not copied, so its buffer most likely cannot be stack allocated, but is recommended to be a buffer that is long-lived, at least as long as the TextLayer is part of a visible Layer hierarchy.

time_str is not copied when passed to text_layer_set_text; the effect being that it goes out of scope and is never displayed on the watch face. The solution is a set of string buffers referenced statically – I used a static char pointer array, and malloced/freed the buffers in window_load/window_unload.

// at the top of the file
#define TIME_LABEL_LENGTH 5
static char *time_strings[4];

// in the window_load() function
  for (int i = 0; i < 4; i++) {
    time_strings[i] = malloc(sizeof(char[TIME_LABEL_LENGTH]));
  }

// drawtext() changes to:
  snprintf(time_strings[i], TIME_LABEL_LENGTH, "%dm", minutes);
  text_layer_set_text(time_text_layers[i], time_strings[i]);

The iOS App

Pebble integration doesn’t require a a substantial amount of code – drag in the frameworks and pull a PBWatch reference from PBPebbleCentral.defaultCentral().lastConnectedWatch(). Because I want to be able to show the number of minutes until a train leaves, I changed the earlier code from a HH:MM string to a ZonedDate/NSDate in the Haskell & Swift code. I then implemented Pebble communication using the following (dest/pattern is abbreviated to economise on transfer bandwidth & Pebble display size):

    func updatePebble(station: Station, _ times : [Departure]) {
        var pebbleUpdate : [NSNumber: AnyObject] = [
            KeyStation : station.name,
        ]
        for i: Int in 0..<4 {
            let destKey = NSNumber(int: Int32(i * 2 + 1))
            let timeKey = NSNumber(int: Int32(i * 2 + 2))
            pebbleUpdate[destKey] = times[i].shortDescription
            pebbleUpdate[timeKey] = NSNumber(int32: Int32(times[i].time.timeIntervalSince1970))
        }
        self.watch?.appMessagesPushUpdate(pebbleUpdate, withUUID: appUUID, onSent: { (w, Dict, e) in
            if let error = e {
                println("Error sending update to pebble: \(error.localizedDescription)")
            } else {
                println("Sent update to pebble!")
            }
        })
    }

There was a problem though: the Pebble showed around -470 minutes for each train (i.e. 8 hours out – suspicious, as local time is +8:00). Turns out Pebble has no concept of timezone at all. The docs spin this as: “Note that the epoch is adjusted for Timezones and Daylight Savings.” Not sure that qualifies as epoch time, but it was clear the conversion is meant to happen on the phone. The following code sorted the issue:

  let adjustedEpoch = Int(times[i].time.timeIntervalSince1970) + NSTimeZone.localTimeZone().secondsFromGMT
  pebbleUpdate[timeKey] = NSNumber(int32: Int32(adjustedEpoch))

Glorious 1 bit UI

The result (I have a promising future as a watch model). I’ll give it a good workout near the train station over the next week.

Pebble Running

As always, the code is available here, here and here.

A last note: The fact that I’m on week 5 of my watch app development journey and am yet to touch WatchKit is not lost on me. I should hopefully start hitting WatchKit code this week. With a bit of luck.

 

Watch App Development Blog – Week 3

In weeks 1 & 2, I got a Transperth-scraping REST API built and deployed to an AWS-based cloud host. This week I’ll get started on:

Step 3: The iPhone App

Third party apps on Apple Watch are very limited and rely heavily on the companion iPhone app for logic, network access, location etc, so the first place to start with the Watch app is on the iPhone.

The phone app itself will need useful functionality otherwise it’s unlikely to be approved. I have a few ideas for cool features for the phone, but for the moment it can just display the live times for the nearest station. This will require:

  1. Getting the list of stations from the server
  2. Finding the nearest station based on the user’s current location
  3. Getting the live times for that station from the server.

Let’s get started. I’m going to be building the app in Swift (of course). Mattt Thompson of NSHipster/AFNetworking fame has written a Swift-only networking framework called Alamofire, so we’ll start with it.

After setting up the framework following the instructions, I created a new Swift file called ‘ApiClient’. Downloading JSON from the server using Alamofire looks like the following:

func getAllStations(f: [Station] -> ()) {
    Alamofire.request(.GET, "\(hostname)/train")
        .responseJSON { (_, _, json, _) in
           if let json = json as? [NSDictionary] {
                let s = json.map({ Station(dictionary: $0) })
                f(s)
            } else { f([]) }
    }
}

struct Station {
    let id : String
    let name : String
    let location: CLLocation

    init(dictionary: NSDictionary) {
        self.id = dictionary["id"] as String
        self.name = dictionary["name"] as String
        let lat = Double(dictionary["lat"] as NSNumber)
        let long = Double(dictionary["long"] as NSNumber)
        self.location = CLLocation(latitude: lat, longitude: long)
    }
}

I’m using a global function (to be more functional) with a callback parameter that takes an array of stations. The returned JSON array is mapped over to convert the NSDictionary instances into Station values. If anything goes wrong, an empty array is passed to the callback – this isn’t brilliant error handling and will probably change, but it’s clearer to show as-is for now.

The user’s location can then be retrieved from a CLLocationManager, and the nearest location calculated like so:

    func nearestStation(loc: CLLocation) -> Station? {
        return stations.filter({ $0.distanceFrom(loc) <= 1500 }).sorted({ $0.distanceFrom(loc) &< $1.distanceFrom(loc) }).first
    }

    // where distanceFrom is defined on Station as
    func distanceFrom(otherLocation: CLLocation) -> CLLocationDistance {
        return location.distanceFromLocation(otherLocation)
    }

This will filter out all stations greater than 1.5km away, and return the nearest of the remainder (or nil if there are no nearby stations).

From there, we can retrieve the live times from the server using the code:

func getLiveTrainTimes(station: String, f: [Departure] -> ()) {
    Alamofire.request(.GET, "\(hostname)/train/\(station)")
        .responseJSON { (_, _, json, _) in
            if let json = json as? [NSDictionary] {
                let s = json.map({ Departure(dictionary: $0) })
                f(s)
            } else { f([]) }
    }
}

Wait – this looks pretty much identical to getAllStations, just with a different URL and return type. Let’s refactor:

protocol JSONConvertible {
    init(dictionary: NSDictionary)
}

func get<T: JSONConvertible>(path: String, f: [T] ->()) {
    Alamofire.request(.GET, hostname + path)
        .responseJSON { (_, _, json, _) in
        if let json = json as? [NSDictionary] {
            f(json.map({ T(dictionary: $0) }))
        } else { f([]) }
    }
}

// we can then redefine the other request methods as:
func getAllStations(f: [Station] -> ()) {
    get("/train", f)
}

func getLiveTrainTimes(station: String, f: [Departure] -> ()) {
    get("/train/\(station)", f)
}

The UI for the app (which I won’t go through here) is just a regular UITableView with a row for each train at the nearest station. However, it’s worth considering how the app will operate at different phases of its lifecycle – I want the live times to be updated in the background (for reasons that will become apparent later).

While the app is in the foreground, the location updates will come through as normal – I have a 50m distance filter on as this is unlikely to change your nearest station. When entering the background, the app will switch to the ‘significant change’ location service – again, accuracy is not super important so the course-grained significant change should work fine.

For the live train times network requests, in the foreground these will be triggered by a 30s NSTimer, in the background using the background fetch API. I haven’t used background fetch before, but it seems like the right technology to use in the case – allow the OS to decide if the app should refresh its data, based on battery life, network connectivity, and usage patterns of the app.

The various services are switched on & off like so:

    func applicationDidBecomeActive(application: UIApplication) {
        locationManager.startUpdatingLocation()
        timer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(30), target: self, selector: "timerFired:", userInfo: nil, repeats: true)
        timer?.fire()
    }

    func applicationWillResignActive(application: UIApplication) {
        locationManager.stopUpdatingLocation()
        timer?.invalidate()
        timer = nil
    }

    func applicationDidEnterBackground(application: UIApplication) {
        locationManager.startMonitoringSignificantLocationChanges()
    }

    func applicationWillEnterForeground(application: UIApplication) {
        locationManager.stopMonitoringSignificantLocationChanges()
    }

This is enough to start road-testing the app functionality out on the phone, and maybe start formulating a few ideas around the best functionality to prioritise on the watch. Speaking of the watch, next week I’ll have a surprise along those lines. As before, the code is available on bitbucket.

Why Objective-C is doomed

My recent LinkedIn article ended with the conclusion:

One thing that is clear to observers – Objective-C’s days are numbered. Apple management are not shy about killing off technologies that no longer suit them, and once they feel maintaining Objective-C is preventing further improvement, it will be given an official end of life. This won’t happen overnight, but it will happen, and Objective-C developers should ignore the warning signs at their own peril.

This resulted in an animated comment discussion – many developers who are professionally (and emotionally) tied to Objective-C would be understandably resistant to the concept of its eventual demise. However, I still feel the writing is on the wall for Objective-C. I think this particular aspect deserves a bit more treatment, so I’m presenting the reasoning behind this viewpoint here.

Swift is transitional, not complementary

Apple have done a fantastic job making Swift and Objective-C code interoperate cleanly, and this leads some to believe that the two languages can coexist indefinitely. I get the feeling they regard Swift in the same vein as CoffeeScript – an alternative syntax to exactly the same code, delivering some convenience and terseness to the source without fundamentally changing the underlying programming model.

However, this isn’t an accurate comparison. Evan Swick posted a detailed look at Swift internals – if I can ineptly summarise, Swift, like Objective-C, is implemented on top of the objc runtime, but differs in key areas like method dispatch. It’s more of a peer to Objective-C rather than just a ‘client language’ compiling down to the same executable.

At a higher level, Swift departs from Objective-C in other aspects. Type-safety and the monadic option type will result in much more reliable apps, the various functional programming aspects will enable us to design software in different ways, and the (intentionally) less capable error handling and reflection features will force us to do so.

For application development, Swift will be a better tool for the job by almost any measure. I can’t envision a situation where, on an ongoing basis (and purely on language merit), Objective-C will be used for some parts of a new application, and Swift will be used for others. Swift is interoperable with Objective-C only so that we (and Apple) can leverage our existing code and slowly transition to the new world, not so that we have additional language choices for development.

Swift code will outgrow Objective-C

Swift programs inherit the standard Cocoa design patterns by virtue of using the Cocoa frameworks, but there are places the Swift programming model is likely to go where Objective-C can’t follow. Some of the issues are apparent already:

  • Objective-C APIs return all objects as optionals (and all primitives as non-optional), making optionals much less useful than they are in vanilla Swift code.
  • Objective-C APIs always deal with non-generic collections, resulting in a lot of typecasts and erosion of the benefits of type-safety.

The other areas Swift will diverge is through the new language features. Generics, ADTs, top-level and curried functions will allow new styles of API, but none of these are available from Objective-C. We’ll initially see third party frameworks adopt a ‘Swift-first’ or ‘Swift-only’ API approach, but I expect there’ll be increasing pressure from developers for Apple to follow suit. Note this isn’t necessarily a rational process – see Erica Sudun’s recent anecdote on ‘Cocoaphobia’.

As an aside, I’ve been trying to think of similar scenarios examples where a platform vendor has replaced their core applications language. The plethora of alternative JVM languages are all community-driven. Microsoft successfully replaced VB6 with .NET, although this involved also rewriting the application frameworks, and of course Win32 and MFC refuse to die. F# was born within Microsoft (Research), but they never really pushed this as mainstream C# replacement – instead they’ve rolled F#-inspired enhancements into the C# language, and spun off F# as a community project.

Ultimately, Apple will not be able to continue advancing the platform & the tools while maintaining backward compatibility with Objective-C. We’ll start to see new frameworks released as ‘Swift-only’, and eventually I’d expect to see Foundation and AppKit/UIKit replaced with Swift equivalents.

Objective-C is not indispensable

It’s true that Swift isn’t capable of seamlessly interleaving C & assembly like Objective-C can, but this isn’t a deal-breaker for an application programming language – most other languages in this category (Ruby, C#, Java, Python, etc) can’t do this either.

The argument I’ve seen posed is that because of the C-level compatibility, Apple will always need Objective-C around to perform lower-level tasks (e.g. build the frameworks and tools), therefore they’ll continue making it available for third party devs to use.

There are a few problems with this:

  • Apple are quite okay keeping tools around for internal use only (e.g. YellowBox for Windows, which was used by WebObjects for a while). There’s a lot more involved in fully supporting a technology for third party developers vs using it internally.
  • The bulk of iOS & OS X’s low level code is written in C, C++, and assembly (like every other platform) – e.g. the kernel, the BSD subsystem, clang & LLVM, the objc runtime, CoreFoundation, CoreGraphics, OpenCL, Metal, WebKit, CoreAudio etc. I think most application developers just see the Cocoa layer and tend to overestimate its importance in the scheme of things.
  • The ‘Objective’ part of Objective-C is even less capable than Swift of writing low level ‘unsafe’ code, so if there is a lot of low-level code in the Foundation framework, conceptually you can regard it as being written in C.

The main reason why Objective-C is used to build the application frameworks is because it’s the language used for building applications. Once we transition to using Swift for applications, it will exist there only for legacy reasons.

The end is at handDoomed! Doomed I say!

I’ll finish with a quote from Bill Gates, of all people:

“We always overestimate the change that will occur in the next two years and underestimate the change that will occur in the next ten.”

Anyone today claiming iOS & OS X developers “don’t need to learn Objective-C” is jumping the gun – it’s going to be difficult to be a good developer without knowing the language the bulk of the code is written in. I expect Objective-C will still be around for most, if not all, of that 10 years.

However, anyone assuming that Objective-C will stick around forever is ignoring the warning signs. Swift and the Mac & iOS platforms will outlast Objective-C, hopefully by a significant margin.