May 27, 2016

Pimp My Code, Book 2: Swift and Dynamism

Introduction to Controversy

Now that everyone and their collective dogs love Swift (it's clinically-proven by leading scientitions!) there’s been some backlash as Objective-C old fogeys fret that if we completely embrace Swift as it is today and throw away Objective-C we’ll lose some of the things we love in our programming environment.

The Things They're Afraid of Losing

As I've seen it stated, the basic issue is that without the Objective-C runtime that's currently bolted onto it like Frankenstein's monster's body was to his head, Swift wouldn't have the ability to cleanly implement, say, Cocoa's "target/action" paradigm for sending commands from the user interface to actual code. Note that Cocoa has two similar-ish ways of handling messages from the interface layer to the program itself: "responder" messages and "target/action" messages (which in the old days corresponded roughly to messages from Display PostScript's events and Interface Builder files' widgets, respectively), and only the latter is dynamic. In the (non-dymanic) responder chain all the methods of input (mouse moved, key pressed, etc.) are predefined on the NSResponder class by Apple; you just subclass them when you need to respond to them.

But since your user interface can have any number of types of input (buttons, sliders, text fields, etc) that do different things in different contexts, the Cocoa authors couldn't possibly enumerate everything any program might do in the NSResponder base class, so they let programmers extend it. One could implement "-makeThisGreen:" on an object in the responder chain, then click in Interface Builder to connect button to "-makeThisGreen:", and presto their method is called whenever the user presses the button.

This process seems really obvious now but it was actually a huge improvement over the state of the art at the time (1988), which was usually to have a single monolithic function that'd be called if the user gave us ANY kind of input, and then you'd have a giant switch statement where you'd handle any possible input from any possible user interface widget. This got very boring.

Now, if the Swift team suddenly chucked the Objective-C runtime, the current implementation of Swift wouldn't allow for this "newfangled" (eg, 1988) interface-to-code technique, since Swift doesn't have the introspection machinery the framework needs to find out if any particular object in the responder chain implements the method the interface is currently trying to call, nor ea mechanism to call a method (determined at run-time) on an object (of a type determined at runtime) with a parameter (of a type determined at run-time). So Cocoa programmers are in a tizzy A TIZZY I SAY at the prospect of going back to the bad old days (eg, 1987).

My feelings about this are complex, because (a) I've been programming Objective-C continuously longer than anyone reading this except maybe two people¹, so you youngsters can just take a seat unless your name rhymes with "Bally Hoser", and (b) normally in these situations I'm the old fogey, but in this case I don't see what the damn fuss is about, unless you're going to get on my lawn, in which case you better watch out for some stern newspaper-shaking.

First Calming Explanation

First off, the Objective-C runtime isn't going away soon. At all soon. Like, Apple's not even at the point where they're releasing Swift-only APIs for new functionality, and won't be this year, at least. So, to write a Swift program for macOS or iOS or watchOS or tvOS or carOS, you have to use Cocoa, and NSObject subclasses, and the Objective-C runtime.

Apple's not even using Swift internally yet for more than like one of their shipping apps. So it's not clear how they'd obviate the Objective-C runtime before they rewrite TextEdit, Preview, Mail, Xcode, iBooks, Calendar, Safari, etc, etc, in pure Swift. Also all our apps are using Cocoa, and they have to keep working, or customers will get mad.

Also this all assumes Apple has rewritten ALL their Cocoa-level frameworks in pure Swift. Which hasn't started yet — there are literally no Apple frameworks with Swift code in them.

Rambling Jokes (skip?)

While I think it's great for the community to discuss what needs to be added to Swift as it grows, I've also seen a lot of violations of what I'll call "Shipley's 2nd Law" (because my ego knows no bounds). The first law, is, of course, "Less code is better code, no code is best code."

Shipley's 2nd Law states, "Groups that have brought you good things in the past will tend to continue to bring you good things, and groups that have brought you shit will continue to pile shit on top." This law proved useful in the 90's when, as I programmed Cocoa, most the world continued to write crappy Windows code, and occasionally they'd say, "Hey, Wil, why don't you write your apps for Windows, think of all the money you'd make!" And I'd say, "I'd make no money because my app would never ship under Windows because it's a shitty environment with a shitty language and shitty frameworks, plus your entire ecosystem is overrun with crapware so even if I came out with a decent app it'd be buried in a sea of me-too apps and nobody would ever find it [editor's note: much like Apple's App Store today]."

And they'd say (stay with me here, this story is relevant to Swift, I'm getting to it) "But the next version of Windows APIs is supposed to be really good! They're working on foundation classes/object-orientation/.COM/.Net/DirectBlah/Explorer everywhere/wobble/something else!"

And I'd say, "Yes, and that'll be shitty, too. Because it's still the same old Microsoft, and they've given us bad, buggy, limited APIs for the last 20 years or so, and there's no reason to think this next thing which they are telling us is great will actually be great." And I'd always be right, which, believe me, is as much a curse as it is a blessing. Heavy is the head, my children.

Second Calming Explanation

So, although it's sort of an appeal to authority (which is bad, mmmkay), I urge anyone who's speculating about Swift's possible future missteps to look hard at the team that's brought us LLVM, clang, ARC, and Swift, and think, "it's pretty likely they'll handle this," and not, "OH GOD EVERYTHING I LOVE IS ENDING." (The latter of which, incidentally, is my nightly mantra.)

I Didn't Steal Your Horse, and Also It's a Lousy Horse

Really, the current target/action system is kind of crappy. For years the only interface was of the form:

- (IBAction)doSomething:(id)sender;
And then you'd have to unpack any value you wanted from 'sender' except as Cocoa got more complex you kinda had no idea what 'sender' was and what it'd respond to, so you'd have to check if it were a button or a menu item to see what kinds of values it might have. This was pretty ugly and also if you wanted to perform the same action from your own code you instead of the interface you could call your IBAction method yourself, but then you couldn't pass it a parameter unless you wrapped it in a dummy NSButton or something and THAT was horrible, so everyone would eventually end up writing code like this:

- (void)doSomethingWithState:(NSInteger)state; { // here we actually do something good! }
- (IBAction)doSomething:(id)sender; { assert([sender respondsToSelector:@selector(intValue)]); [self doSomethingWithState:sender.intValue]; }
And then we'd have multiple action methods written just like this, and we'd end up grouping all our these action methods together in one part of the file, like:

- (IBAction)doSomething:(id)sender; { assert([sender respondsToSelector:@selector(intValue)]); [self doSomethingWithState:sender.intValue]; }
- (IBAction)doSomethingElse:(id)sender; { assert([sender respondsToSelector:@selector(intValue)]); [self doSomethingElseWithState:sender.intValue]; }
- (IBAction)doStringThing:(id)sender; { assert([sender respondsToSelector:@selector(stringValue)]); [self doSomethingStringThingWithString:sender.stringValue]; }

And, hey, guess what, this ends up being just a bunch of template code which is a pain to maintain and honestly, it kinda looks like a giant switch statement, doesn't it?

Now, I don't want to go back to switch statements, either, I'm just saying there's room for improvement on this paradigm. Like, IB could be extended to take blocks of code, for instance, so a single button click could have multiple effects and take multiple UI inputs into account. Sure, we'd still need introspection, but I don't think anyone's argued Swift shouldn't get it.

Dynamic Methods that Are NOT Target/Action

There may be other use cases for lots of dynamic features, but honestly target/action is the only time I ever even think about them any more. Other forms of dynamic calls in Objective-C were pretty much obsolesced years ago, when ARC came along and the compiler was all, "Hey, you can't pass arbitrary C types to methods I can't analyze, because I don't know if I need to retain them or not!"

So if you want to blame anything, blame ARC, not Swift. It's still Lattner's bailiwick, so, you know, you don't even have to change who you're mad at.

Concluding Thoughts About Swift

I'm writing an app that is pure Swift, which I ported it from an Objective-C version a little over a year ago, so I can directly compare the two. During porting my source code shrank and ran much, much faster. Both languages are Turing complete, so I won't insult your intelligence saying you can do more in Swift. But I will state that you can do more in a given amount of time. And I don't believe there's anything I could do in Objective-C that I can't do more beautifully in Swift.

I am better Swift programmer after a year and a half in Swift than I was in Objective-C after 26 years.



¹ slight exaggeration for humorous effect, but seriously, since 1988

Labels: ,