Dynamic Swift Comments on Hacker News

I don’t generally point to (or even read) comments on Hacker News — because in the past I’ve found that they’re like other comments on the web.

However, I think the Hacker News comments on Michael Tsai’s Dynamic Swift post are worth reading.

Ash and Marcel

Ash Furrow, Adulterated Objective-C:

…maybe it’s not important to re-invent Objective-C’s dynamic runtime in Swift. The language is far more static than Objective-C. And besides, a dynamic runtime is only a set of tools used to solve problems. In a different context, like Swift, different tools might be better suited.

Problems currently solved with dynamic programming should have solutions just as good (if not better) in Swift and future Swift-only app frameworks. And “better” can’t mean, “Yes, it’s a big pain now and inelegant, but the compiler’s happy.”

I’m skeptical that there is a solution short of adopting the same kinds of dynamic features Objective-C has, but I’m only skeptical. I don’t say it’s impossible. I want to be surprised and amazed.

* * *

Marcel Weiher, What's Missing in the Discussion about Dynamic Swift:

The truly amazing thing about KVC, CoreData, bindings, HOM, NSUndoManager and so on is that none of them were known when Objective-C was designed, and none of them needed specific language/compiler support to implement.

Instead, the language was and is sufficiently malleable that its users can think up these things and then go to implement them. So instead of being an afterthought, a legacy concern or a feature grudgingly and minimally implemented, the metasystem should be the primary focus of new language development. (And unsurprisingly, that's the case in Objective-Smalltalk).

Updating Local Objects with Server Objects

I’ve spent much of my career writing apps that store objects locally that come from the web — and that may change on the web, and then need to be updated locally.

This kind of code can be tedious to write. Given a local object and an objectified version of some JSON or XML or whatever, I’d write something like this (translated to Swift for fun):

if localObject.foo != serverObject.foo {
  localObject.foo = serverObject.foo
  changeDictionary[fooKey] = serverObject.foo
}
if localObject.bar != serverObject.bar {
  localObject.foo = serverObject.bar
  changeDictionary[barKey] = serverObject.bar
}
// multiply above a dozen times and for several different classes
return changeDictionary

Maybe you spotted the bug in the above, and maybe you didn’t — and that’s part of my point. I’d write this code using copy-and-paste, and then go over it visually, line-by-line, to make sure it’s right, and sometimes I’d miss something anyway.

If foo and bar are both strings, for instance, the above would compile and all would be well. Except that I’ve written a bug where localObject.foo = serverObject.bar.

Eventually I found a better way — some code that I can write once, and that can’t have the bug I wrote above. See this gist.

* * *

The solution is pretty simple. You have two objects — which may or may not be the same class but where the properties have the same names — and then loop through an array of mergeable property names. (The two objects don’t have to have the exact same list of property names. They just have to have the mergeable property names and types in common, since those are the ones we care about.)

Then there’s a method that compares the local and server objects. For each property name in the list, it gets the localValue and serverValue using valueForKey:, and then compares them (via isEqual:).

When they’re not equal, then it uses setValue:forKey: on localObject to give it the serverValue. It also adds the property name and new value to a change dictionary. (That dictionary could be useful for efficient database updating, sending notifications, etc.)

There are places this could go wrong, and where the compiler wouldn’t complain. In the gist there’s this line:

static let mergeablePropertyNames = ["dog", "cat", "zebra", "numberOfAnimals", "fedTheTigers", "attendingDoctors"]

Obviously that line has to be maintained, and it has to be true that both local and server objects have those properties and those properties have to be of the same type. Yes.

However, you’re going to notice any error at runtime pretty quickly, since you’ll get an exception — while you may not notice the localObject.foo = serverObject.bar bug from the manual version any time soon.

Best thing: you can reuse that updateLocalObject​WithServerObject method for all the various local/server object updating you need to do in all your apps.

* * *

Even though we’re writing in Swift here, the solution uses the Objective-C runtime and KVC.

KVC is both awesome and to be used sparingly. Most of the time you want to write standard code that the compiler can better check — you want to write foo.bar = true. Absolutely.

But there are those occasional critical cases where KVC creates more reliable code that’s easier to maintain, debug, and reuse. This is one of those.

Layers Conference

In case you haven’t seen it mentioned already — Layers is a design conference in San Francisco taking place the same week as WWDC. It’s “like a party, but for learning,” and promises snacks, and people I respect tell me it’s wonderful.

Be sure to scroll down and look at the list of presenters. It’s amazing.

Gus and Craig

Gus Mueller:

Being able to write dynamic, responder chain-like code in Swift or Objective-C (or whatever) is extremely important to me. And if I had to give one reason why it would be this: Dynamic code is less code, and less code means fewer bugs and maintenance.

Craig Hockenberry:

The community around Swift’s evolution is amazing. The language is improving quickly and dramatically thanks to talented developers inside and outside of Apple. It’s a remarkable open source project.

My concern is that there isn’t a corresponding discussion about the things we’re going to build with this new language. As you’ve just seen, frameworks are important, yet there is no uikit-evolution mailing list. There is an imbalance between the tool and the craft.

What I’m Doing With These Articles

In case it’s not clear: with recent and future articles I’m documenting problems that Mac and iOS developers solve using the dynamic features of the Objective-C runtime.

My point isn’t to argue that Swift itself should have similar features — I think it should, but that’s not the point.

The point is that these problems will need solving in a possible future world without the Objective-C runtime. These kinds of problems should be considered as that world is being designed. The answers don’t have to be the same answers as Objective-C — but they need to be good answers, better than (for instance) writing a script to generate some code.

(Swift is just one part of that future world. The part I care about more, far more — as much as I love Swift — is any app frameworks built on Swift. But the design of Swift has an impact on the design of those frameworks.)

There’s a second point: many people writing in Swift right now seem not to realize the extent of their reliance on Objective-C’s dynamism. Even if you don’t write a line of Objective-C, you’re running a ton of code that is Objective-C, and that dynamic runtime, and the app frameworks built on top, is what makes your apps actually work. This is kind of a “duh” thing, I realize, but it’s worth the reminder.

Next up (probably Thursday or Friday) will be a thing on easily updating local model objects with data downloaded from a server. And then more to come.

0.0001% of the Way to Core Data

This post isn’t about Core Data — but it’s a well-known example of adding methods at runtime, so I’ll use it.

Let’s imagine you want to make something like Core Data. You have a bunch of different model objects backed by a database. You want to implement faulting — that is, an object’s properties are nil in memory until you actually access one of the properties, at which point it pulls its values from the database.

You have some options for how to do this. You could skip properties entirely and override valueForKey: and setValue:forKey:, and always access the values that way. Your overridden versions of those two methods would handle faulting. But this makes for a bunch of strings in your code, and it isn’t nearly as readable as obj.foo = bar.

Alternately you could create all the getters and setters by hand, for all the properties in all your data objects, and be sure to include a hypothetical fetchIfNeeded call in every single getter and setter. That’s a ton of copy-and-pasting. It gets you obj.foo = bar syntax, but it’s also tedious, error-prone, and painful to maintain. (You could use macros or a script to make this a bit easier, but it’s still sucky.)

Instead, you want to solve the problem reliably and once. You want maintenance of your data objects to be no more difficult than adding and removing properties and their associated @dynamic declarations. (Actual database schema maintenance is a separate consideration — that’s in the remaining 99.9999% of the way to Core Data.)

While I can’t say for sure how Core Data adds methods at runtime, I can say for sure that this particular magic is available to us and not just to Apple. Which is as it should be — we have every right to expect that same power.

How to add methods at runtime

Here’s the plan. We’ll create a DataObject class that adds methods. Your model classes will inherit from DataObject. Your model classes will be free of anything odd — all that stuff goes into DataObject, which you can write once and then forget.

You need a class method — + (BOOL)resolveInstanceMethod:(SEL)selector — that will get called when the runtime is looking for the accessors for your properties.

If the selector is a getter or setter for one of your properties, then your resolveInstanceMethod will call class_addMethod, which appears in objc/runtime.h, with the appropriate parameters, including a C function that is the actual implementation of the method (an IMP).

Example: class_addMethod(​[self class], selector, (IMP)getterIMP, "@@:");

It’s actually unbelievably easy. See my sample demo app on Bitbucket, which shows the basics.

(You can get fancier than I did — you might start by using reflection to get the list of property names instead of using an array, for instance. You could handle non-object types.)

But why?

Maybe you’re thinking, “Great, but I already just use Core Data and don’t need this at all.”

First: congratulations on using Core Data. It’s the right move.

Second: imagine a different scenario — think of DB5. DB5 lets you put a bunch of appearance definitions — strings, colors, edge insets, and so on — inside a plist so that you don’t have to scatter all these values throughout your app. It’s a nice utility, but its API is all string-based, which means plenty of room for typos and errors.

Imagine, instead, if you could define the set of appearance properties your app needs. This would make errors less likely: the compiler would be your friend. But you definitely don’t want to go through the error-prone work of actually creating a hundred accessors — you want those created automatically for you at runtime.

As you might imagine, someone’s actually already done that: it’s how Omni’s OAAppearance class works. And, as a bonus, there’s an Xcoders talk from Curt Clifton that goes into detail. (The audio isn’t great, but the talk is worth it anyway.)

And, as a double-bonus, OAAppearance is open source on GitHub.

Responder Chain Followup

Joe Groff reminded me that checking for protocol conformance is Swift’s respondsToSelector equivalent:

protocol RespondsToCopy { func copy() }
if let copier = responder as? RespondsToCopy {
  return copier.copy()
}

And, later, Ölbaum asked:

Wow, so all this fuss around lack of performSelector in Swift is just about laziness to use protocols?

My terse — and jerky, because it was the morning — answer was “No.” This post is the longer answer that the questioner deserves.

Let’s Do This Thing with This Thing

Take it from the top.

You’re in IB wiring up a menu item or button to First Responder. You set the selector as copy: — or, better yet, because I think people may get confused by using a common command, let’s say the selector is goFishing:.

To make this work with protocols instead of respondsToSelector, you then also type in a protocol name: RespondsToGoFishing. Or maybe the responder chain automatically generates that protocol name based on the selector. (Either way.)

In your code you define a RespondsToGoFishing protocol (as in the first line in Joe’s example above) and implement it in the various classes that can goFishing.

Then, after you’ve launched the app, you tap the button or choose the menu item. The responder chain walks its hierarchy, looking for the correct implementor.

What does the responder chain code have at this moment? Four things: a responder hierarchy, the sender (menu item, button, etc.), a reference to a protocol, and a selector.

(Let’s assume that some previous machinery turned the typed-in protocol name and selector from a string into more-usable types. Which assumes some kind of protocolFromString and selectorFromString methods, or some form of compilation.)

So the responder chain code looks something like this:

if let actionImplementor = responder as? protocolReference.Self {
  actionImplementor.​performSelector​(selector, withObject: sender)
}

(I’m never sure if it’s Self or what. The above may not be strictly correct. But you get the idea.)

Conceptually this is not much different from the Objective-C version — the main difference is that it adds an entity (a protocol) which wasn’t previously needed. But I’m cool with that. (Maybe. It means adding a whole bunch of protocols — possibly one for every action method.)

For reference, here’s the Objective-C version:

if ([responder respondsToSelector:selector]) {
  [responder performSelector:selector withObject:sender];
}

That’s fine, that totally works, but…

The point of my previous article was to imagine a responder chain written in Swift minus the Objective-C runtime.

So the above solution — with actionImplementor.​performSelector​(selector, withObject: sender) — won’t work in this hypothetical world, since performSelector is an Objective-C thing. And then there’s the need to convert from strings (protocol, selector) in IB to an actual protocol reference and a selector.

But — this is all just to say that perhaps it’s too early to be concerned with things like this, since we do have the Objective-C runtime (and AppKit and UIKit). At some point in the future, I imagine we’ll see Swift-minus-Objective-C-runtime app frameworks for Mac and iOS. And I will be very interested to see how these kinds of problems are solved.

I stand ready to be amazed, knowing it may be years from now.

In the meantime, though, it’s fun to write about.

The Case for Dynamic-Swift Optimism

In my last two posts I brought up my nervousness over not seeing dynamic features in Swift yet — because I argue that Objective-C’s runtime is a big part of what makes AppKit and UIKit awesome, and any future frameworks need to be just as awesome.

I should perhaps not be so nervous.

I strongly suspect that Swift is designed with dynamic programming in mind — and is potentially better for dynamic programming than Objective-C. (Since, after all, it drops the C, is a fresh start that learns from the past, doesn’t have to worry about breakage so much — and, hey, look at that team.)

If this is true — and I don’t have an easy way to evaluate it, so I’ll assume it — then we can assume that the reason it hasn’t been an issue so far is that we already have the Objective-C runtime. (Even if all your code is Swift, your app is using that runtime.)

So there’s no rush.

However, open-source-Swift means there are environments where the Objective-C runtime is not present. Which means the calls for dynamic programming may come from, ironically, web developers running Swift on Linux.

But there’s an even bigger point: lots of people in Apple write apps. They’re the first consumers of Swift and hypothetical Objective-C-runtime-free frameworks. They’re going to need to be able to write their apps too.

So I shouldn’t be so nervous.

A Hypothetical Responder Chain Written in Swift

My piece The Tension of Swift started out as a piece talking specifically about the Responder Chain and how you might write it in Swift. It got almost entirely rewritten before being published.

I was thinking specifically of commands like Copy, where a menu item or whatever has an action selector copy: but no target — which means the framework travels the Responder Chain until it finds a responder that implements copy:, and then calls it.

(Note: it could be any selector at all — goFishing:, for example — but the Copy command makes a good example.)

A simplified version of the framework’s implementation looks like this. Given the first responder, a sender (the menu item or button or whatever), and a selector:

while(responder != nil) {
    if ([responder respondsToSelector:selector]) {
        [responder performSelector:selector withObject:sender];
        break;
    }
    responder = responder.nextResponder;
}

So I tried to come up with a Swift version — a Swift-minus-the-Objective-C-runtime version — and I came up with this (from my unpublished first draft):

You’d have each object in the Responder Chain conform to a protocol with two methods:

func respondsToCommand(command: String) -> Bool
func performCommand(command: String)

Then, in your menu items (and buttons and wherever), you’d set a command string like “copy” and “goFishing” and so on. ​ The Responder Chain would then walk its hierarchy and ask each object if it respondsToCommand, and when it returns true, if would call its performCommand method with the command string.

​You might slice this up differently than I did — you might very well make it more Swift-like — but in the end I think you’d still end up passing strings and doing switch statements based on an action string.

The Swift version is definitely safer, though the Objective-C version was effectively safe, because you’d have to go out of your way to make it fail, and you’d find out pretty quickly. (Though, yes, at runtime.)

The Swift version also adds more housekeeping. The Objective-C version requires you to implement a copy or goFishing method, but no more than that. A major part of the beauty of AppKit and UIKit is not having to do the kind of housekeeping that other app frameworks require. (We start out on the 20th floor of the building.)

There’s also an aesthetic argument to make. Objective-C’s message-passing is a perfect fit for this exact kind of problem. In my Swift version I’ve recreated a simplified message-passing, but not as a feature of the language, as more of a thing bolted-on in order to solve a problem that really wants to be solved by message-passing. This is, I think, undeniably less elegant than the Objective-C version.

* * *

I feel like I have to say it every time: I’m writing all my new code, both at work and on personal projects, in Swift, and I enjoy it, and I don’t want to write Objective-C any more.

And: I appreciate Swift’s type safety — I’m even learning to love optionals. But the thing I keep coming back to is that great app frameworks require languages with dynamic features. And, as an app writer, I’m less concerned with the language than with the frameworks.

Objective-C the language was always a bit of a odd duck, though lovable — and it allowed these truly magnificent frameworks to be built, and using those frameworks has been one of the joys of my career.

I want the app frameworks of the future to be just as wonderful. More so.

* * *

PS Also see Daniel Jalkut’s optimistic take, Manton Reece on Apple’s mindset on Swift dynamic features, and Rob Fahrni on A Swift Only Future?.

Rob writes:

This is the beginning of the radical departure from Cocoa I’m hoping for. A day when all Framework code for building Mac and iOS applications is void of Objective-C, or mostly void of it. Not that Objective-C and Cocoa are bad, they’re not. It’s just time to move on…

Will there be pain? Absolutely. Is it possible to overcome it? Of course it is. It will take a lot of work and planning for companies to move to Swift and Framework X, but it will take years for Apple to get there, so we all have quite a bit of time.

* * *

Update 12:00 pm: I keep wanting to find a way to express my nervousness succinctly. I think it’s this: there’s a difference between a language that makes writing high-quality code easy and a language that makes writing high-quality apps easy.

Archive

Ads via The Deck