Beyond Objective-C, Beyond Swift
I had been writing software for less than a year when Apple introduced Automated Reference Counting (ARC) in iOS 4. Even in that short span of months I had struggled often enough with manual memory management (retain/release) that I immediately grasped the significance of ARC. I’ll never forget the figure that accompanied the transition guide documentation:
It perfectly illustrated the benefits of ARC. ARC didn’t make code shorter through syntax niceties or compacted expressions. It obliterated whole swaths of code throughout a project. An entire layer of error-prone state management was reduced to an almost invisible implementation detail.
ARC seemed too good to be true. Granted, to this day there are still some areas where memory management requires finessing, but for the most part it is quite close to magic — especially for anyone who remembers what it was like to write iOS software before ARC.
Progress is Offensive
During the first few years after the ARC introduction, mastery of old-fashioned manual reference counting was a point of pride among seasoned developers. Using ARC was somehow not “real” Objective-C programming. The kids these days and their ARC. That attitude is one that I don’t encounter much anymore. By the time Apple transitioned OS X to ARC, the stigma had all but disappeared.
Advances in software development are offensive to the practitioners of the technologies made obsolete. The new technology seems childishly simple in comparison to the complexity of what it replaces. At the risk of overgeneralizing, I think this reaction is natural. When you’re passionate about your work, you tend to fall in love with its idiosyncrasies, even those that might actually be burdensome.
Software development advances via abstraction. Best practices from lower levels of infrastructure are codified, automated, and optimized for common scenarios. It’s this abstraction and simplification that is the source of offense for the champions of older ways of working. When it’s done right, a new technology automatically handles tasks which previously required significant planning and effort. No one wants to see their expertise made obsolete — until they realize that the new layer of abstraction they thought was childishly simple is actually the building blocks for new complex things they’d never have been able to build with the old tools.
That’s what I want from an Objective-C replacement, but it isn’t what we get from Swift.
A Feeling of Disappointment
Some people are huge fans of Swift already. Others are ambivalent, doubtful, or outright opposed. I would place myself among those in the ambivalent group. I haven’t been able to articulate my thoughts and feelings yet. I think I can now.
There’s a lot to like about Swift. I especially love enums, string handling, the Playground REPL. Swift has clearly been designed with thoughtfulness and care. But nevertheless I can’t shake a feeling of disappointment. The best way I can sum it up is:
Swift solves problems that I don’t really have, and the problems I care about most Swift doesn’t solve.
Let’s use Apple’s Swift landing page as the canonical reference for the reason Swift was made. Here are the Swift features that Apple lists first (not including features that Objective-C had already, more or less, like automatic memory management):
Inferred types – Inferred types make code cleaner and less prone to mistakes.
Modules – Modules eliminate headers and provide namespaces.
Closures & Functions – Closures are unified with function pointers.
Tuples – Tuples and multiple return values.
Generics – That’s all it says. Just “Generics.” It’s implied that the usefulness of generics speaks for itself.
Structs – Structs that support methods, extensions, protocols.
Functional Programming Patterns - For example,
These are all demonstrable improvements over Objective-C. Let’s set aside the tired debates about the merits of generics, inferred types, functional programming patterns, etc. and agree that yes, Swift is better at many things than Objective-C.
So why am I disappointed? My disappointment with Swift is because all the items in the list above are only iterative improvements. None of them address the biggest challenges I face as a developer. To explain why, let me first describe the way I work. Then I’ll return back to Swift.
How I Work: Coding in the Air
I build all my software in a manner I call coding in the air. It began as a fruitful working relationship with Jamin Guy, my colleague at Streamweaver and Riposte. We would sit in Jamin’s living room and discuss architecture at length. Sometimes sessions would last for five or six hours. Neither of us would be touching a keyboard. We’d build up and tear down APIs in our minds, rattling off class names, property lists and method signatures over and over, each time a little different. Our mouths were sore from repeating the same words. Gradually the right patterns revealed themselves.
At the end of these sessions, the implementations were so clear in our minds that the actual work of writing software was not much more than typing.
It takes enormous amounts of concentration to build software in this way, but it’s worth it — especially if you have the benefit of a like-minded partner. Other than an uninterrupted block of time, there’s virtually no cost to coding in the air. As soon as an idea doesn’t work, it vanishes. There’s no code to rewrite or files to delete.
Coding in the air shifts your focus away from implementation details and onto architecture. It’s architecture that is the hardest and most important part of any project. It’s the part that takes the most time to implement — and the most time to reimplement should you paint yourself into a corner with a bad assumption early on. Coding in the air reduces the risk that you’ll need to reimplement something.
Implementation is the Bottleneck
When at last the time comes to fire up Xcode, the implementations are fairly clear in my mind. But this is where my problems really begin. Every project I’ve worked on involves rewriting the same kinds of implementations, but this code isn’t re-usable. Things like:
Databases & Persistence – Frameworks like FMDB or Core Data are reusable, but not the classes that actually make use of those frameworks. Classes have to be written that insert, update, and fetch project-specific objects into and out of those frameworks. Other classes have to be written to provide caches of those objects for uniquing and performance. If you want your table views to delay pending changes until after scrolling has completed, you’ll need to use something other than an NSFetchedResultsController to manage the pending changes. Layer upon layer of model controllers, for lack of a more graceful term, are required to serve as the “goo” between what your app does and where its data is housed. Let’s not even mention dealing with multi-threaded database access, migrations, and merging.
JSON & RESTful APIs – Networking tools like NSURLSession or AFNetworking are reusable, but not the classes that make use of them. Data must be converted to dictionaries, and dictionaries converted to objects. Each step in that pipeline is rife with potential errors that need to be safely caught, responded-to, and propagated back up to the top level of the application. The same complexities exist in the opposite direction, for outbound requests that send local data to a remote API.
Triggers & Responses – Events that trigger responses have to be wired up. KVO, when done correctly, involves a non-trivial amount of boiler plate. Notifications have to be posted, with the correct user information. Observations of those notifications have to be written wherever needed. If you make a mistake with KVO or notifications, you might get a crash. Many times things just won’t work, and no amount of linting will warn you. Your best hope is to set up integration tests with UI Automation and hope that your app design doesn’t break them too often. ReactiveCocoa is a daring third-party approach to simplifying some of these implementation issues.
UI Layout – Whether you use springs & struts or AutoLayout, either way you still have to explicitly instruct your view classes how they should arrange themselves. Long hours are spent writing and correcting the math behind these arrangements, especially now when there are so many devices to account for. Nothing just works. It’s all dependent upon minutely detailed instructions. It feels like instructing a blindfolded person how to assemble a model plane. Every app is a new plane.
These are just some of the time-consuming tasks we all face when writing Objective-C applications. They take a long time to do well, and involve a great deal of repetitious, brute-force work. These tasks deplete limited resources. Developer time is the biggest expense subtracted against a startup’s bottom line. It’s also mentally and physically taxing. It’s hard to stay focused on the core problem that matters most: building something that users love.
Back to Swift
Swift doesn’t reduce the time and energy it costs to implement the boilerplate goo that comprises our applications. Even factoring out the learning curve (which is steep), Swift merely shifts the locus of debugging from runtime into compile time. It solves some common implementation mistakes with Objective-C, but it doesn’t make whole applications faster to implement. Saying Swift makes programming easier is like saying that iOS 8 extensions make it easier to use an iPad the way that Federico Viticci does — more straightforward, perhaps, but as a workflow it’s just as complex.
The net result will be this: Objective-C developers will spend the next several years transitioning to the new syntax and assumptions in Swift. When that transition is finished we will likely still be where we left off in 2014. We’ll still be spending long hours writing boring, buggy, non-reusable implementations of persistence, networking, trigger/response, and layout code.
What I Want
On a continuum from the abacus to punch cards to Objective-C, Swift is only an iterative improvement upon Objective-C. Perhaps someday Swift will serve as the underlying language behind the kinds of solutions I’d like to see. Regardless of how it happens, here’s some of the things that I want:
Automated Persistence – I don’t want to have to think about how to implement persistence ever again. I just want to toss a model object into a bucket and trust that it will be stored, cached, merged, and uniqued. I should be focused on describing the properties and relationships between objects, and the way I want them to be grouped. Everything else should be an invisible implementation detail.
Automated RESTful APIs – Once I’ve instructed my application how to map an API response to a model object, the networking and JSON conversion code should be handled behind the scenes without any effort from me. I want to focus solely on how those items should be presented to the user.
Expressive Triggers & Responses – I want to describe the responses to events and triggers in a syntax that derives from the intent of the response. I don’t care how those linkages are implemented, only that they’re implemented in ways that won’t break through common refactoring mistakes.
WYSIWYG UI Editing – I don’t care what the math is behind a view hierarchy. Just like Disney keyframe animators handing off pencil sketches to armies of interns, I want to manually position some example views in Xcode, for a handful of edge cases, and let Xcode handle all the implementation decisions. That would truly be worthy of the name AutoLayout.
I want whatever comes after Objective-C to obviate whole layers of architecture the same way that ARC made memory management a concern of the past. I understand that what I’m asking for is more than just a programming language. But that’s because the biggest obstacles holding back Objective-C development can’t be solved by a language alone. We need a new paradigm.