I find it fascinating to compare equivalent classes/APIs from around Apple’s frameworks, particularly in the transition across from OS X to iOS, to see how they made it (yes, I’m a sad old git, guilty as charged). Often there are changes on the iOS side which I suspect come from having learnt what didn’t work out so well on OS X — or at least what could be improved — and applying that to the new technology. Today I thought I’d take a quick look at NS/UIResponder.
Here’s some of what one could consider to be the “core” functionality of NSResponder:
It seems to me the API is based around a lot of Objective-C’s philosophy of the time. In general, you send some sort of message, and leave it to the system to deliver and trust everything works out from there. Typically, this is all handled by NSApplication, sending actions to a specific target or the first responder, but there are the NSResponder methods above for when you need to drop down a bit lower.
Now compare to the story on iOS:
There’s a pretty clear difference here. At the application level, the API still deals in terms of message sending; fire the message off and trust the receiver does the right thing. But lower down in UIResponder it’s a different story. We instead have API to search for a target and explicit hooks to control/override that process (granted, that didn’t fully come in until iOS 7).
Sure, NSApplication has a similar style of thing where you can find the target of an action, but that’s only of any use really if searching from the first responder. In a modern app, I find myself needing to search from arbitrary points quite often, regardless of what is currently the first responder, and with that the NSApplication simply isn’t good enough.
And there’s more! If you are doing a bit of custom action dispatch, your code typically looks like this:
The slight downside here is that I am repeating myself across the two lines. But, by breaking the code up, I’ve gained a few advantages:
- If no target is found, it's clean and obvious, and I can handle that with more code accordingly.
- By messaging the target directly on line 2 (as opposed to calling -performSelector: or a variant of), I can step directly into the method implementation in the debugger, making my life a lot pleasanter.
- Perhaps crucially, the system is now open to an arbitrary number and types of arguments. The OS X system limits you to a single sender argument, which must be an object. But on iOS you can have as many arguments as you like, and some of them can be BOOLs etc!
I hope OS X moves to this model soon as I think it’s pretty superior, and have of course filed a radar accordingly :-)
It’s also fairly straightforward to write your own category that mimics a fair bit of the above a lot of the time.
OS X does have one little trick left up its sleeve:
Faced with some of the brittleness of the dispatch system I’ve discussed above, Apple added this API in OS X 10.7 (about the same time as iOS 5 for cross-reference). It’s certainly rather helpful for tacking on extra behaviour.
Before, I’d find myself creating a window controller which implements a lot of action methods solely to message the real object that’s responsible for handling them. And then you do it multiple times, to implement things like UI validation. supplementalTarget… means you just have to write a single implementation that looks up the correct object — typically a view controller these days — and return it to have the system send all relevant messages there.
I think it interesting that iOS hasn’t gained this API, but given how -targetForAction:withSender: is there for us, and documented to be overridable, I think we have another example of Apple’s engineers learning what did and didn’t work and finding something better. By overriding -targetForAction:… you get all the benefits of -supplementalTarget… without having to go via the application to actually use the API as a client!