2016-03-10

1Raise your hand if you’re an Objective-C developer and you’re sick of writing this:

if([obj respondsToSelector:@selector(foo)])
{
    id foo = [obj foo];
}

Unless you’ve switched to full-time Swift, in which case you can do this:

let foo = obj.foo?()

I want that, but for Objective-C.

id foo = [[obj please] foo];

I had been thinking about doing this for years, but only actually tried recently.2 It turns out it’s not very difficult, but we’ll have to cover a lot of topics first:

  • Objective-C Message Dispatch
  • Forwarding
  • NSInvocations
  • Boxing

ObjC Message Dispatch — A (largely incomplete) summary

Messages are the fundamental feature of Objective-C, far more than objects and class inheritance; classes are just the basic mechanism for dispatching messages. It probably shoud have been called “Messaging-C”.

Sending a message to an object goes like this:3 4

  1. Messages are compiled to objc_msgSend().
  2. objc_msgSend() looks for an implementation in the Class’s Methods
  3. No implementation? Is there another object that can handle it?
  4. Nobody? Maybe we can just create a new Method out of nowhere?
  5. Well then, doesNotRespondToSelector:. ☠️.

Let’s examine it step by step.

objc_msgSend

At compile time, clang replaces all messages with objc_msgSend() calls.

[obj foo]
➡️ objc_msgSend(obj, "foo")

There are a lot of gory details regarding how parameters and return values are passed. In particular, methods that return structs are compiled to a different version of objc_msgSend():

[view frame]
➡️ objc_msgSend_stret(view, "frame")

The reason for this is that functions that return objects (id) put their return value in a register. This isn’t possible for structs, for which we must allocate space on the stack. 5

Inside objc_msgSend(), the regular method lookup works like one would expect: it uses the object’s isa (its class pointer) and the SEL (the selector, or method name6) to find the IMP (the method implementation) which is just a function pointer of the form:

*(id self, SEL _cmd, ...)

The two first args are the implicit arguments available in all Objective-C methods: self, and _cmd. With the types of the other arguments and the return value, they form the Method Signature, which is expressed as an Objective-C Type encoding. For example:

- (void)foo:(int)bar; // encodes as "v@:i"
  • v means the return type is void,
  • @ the first argument is an id (self),
  • : the second argument is a SEL (_cmd),
  • i the third argument (the first explicit arguemnt) is an int.

objc_msgSend then jumps to the address of the IMP function, and program execution continues from here7.

Forwarding

That was just the regular case. But what if there’s no IMP? Enter Message Forwarding.8

In the forwarding mechanism, messages are objects, too. An NSInvocation is an object that represents an actual message (a selector) send to an object (its target, or receiver), with its arguments.

NSInvocations are strange and beautiful beasts. They can be seen are blocks, or closures: they are Function Objects. But NSInvocations can also be worked with entirely at runtime. As clang emits the correct call to obj_sendMsg, NSInvocation setups the call, but at runtime.9

Using NSInvocation goes like this:

NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:/*...*/];
invocation.target = (id) ;
invocation.selector = (SEL) ;
[invocation setArgument:(void*) atIndex:(NSInteger)];
...
[invocation invoke];

This iss how you setup an NSInvocation yourself in order to send a message.
When you receive an invocation, that is how you set the return value for the caller:

...
id result = ...
[invocation setReturnValue:&result]; // a void*

NSInvocation copies the result, and uses the method signature to return it properly for objc_msgSend or objc_msgSend_stret.

Boxing

Let’s look at our proposed solution again:

id foo = [[obj please] foo];

It looks like -foo returns an id. What about those?

CGRect r = [[obj please] frame];
NSInteger i = [[obj please] count];

Moreover, I’ve purposedly left out an important detail:

What do we return if the method isn’t implemented?

When the method returns an object, nil is the easy answer, but what about other types? We need a way to specify a fallback value. Something like:

[[obj pleaseOtherwiseReturn:42] count];

So. Boxing.

Boxing is what we use whenever we need an object, but really only have a scalar or a struct, or anything non-object.

In Objective-C, Boxing is “putting stuff in NSValue” — NSNumbers are NSValues, by the way. It’s used to store ints in NSArrays.

Key-Value Coding uses it all the time: the KVC primitives return objects, but sometimes the underlying values are structs. KVC automatically boxes them:

CGRect r = [view frame];
NSValue * v = [view valueForKey:@"frame"]; // Get the CGRect with -CGRectValue

Unsurprisingly, NSValue is based on Objective-C Type Encodings. Any C or Obj-C value can be boxed in an NSValue:

typedef struct { int x, y } MyStruct;
MyStruct myStruct = ...;
[NSValue valueWithBytes:&myStruct objCType:@encode(MyStruct)]

That’s what the @( <thing> ) syntax does at compile time. It just creates an NSValue, or directly an NSNumber with the contents.10

@implementation details

Here’s what I’d like to write:

[[obj performIfResponds] doSomething];
[[obj performOrReturn:@"foo"] foo];
[[obj performOrReturnValue:@YES] bar];

The “perform…” methods return a proxy object, that forwards messages to the original target, only if it responds to them. Otherwise, the proxy grabs the NSInvocation and sets a fallback return value.

The public methods are this NSObject category:

@interface NSObject (PerformProxy)
- (instancetype)performIfResponds;
- (instancetype)performOrReturn:(id)object;
- (instancetype)performOrReturnValue:(NSValue*)value;
@end

instancetype, of course, is a lie, to make clang and Xcode happy: the returned object is a proxy. Additionally, we need two performOrReturn- methods to deal with the boxing issues we’ve seen earlier: the underlying objc_msgSend variant is different, the NSInvocation has to match it.

Internally, the proxy object looks like that:

@interface PerformProxy : NSProxy
{
    id _target;
    const char * _returnType;
    void(^_returnValueHandler)(NSInvocation* invocation_);
}

We’ll initialize a PerformProxy using the original receiver as the target and set the correct return type; let’s keep the NSInvocation handler aside for now..

In PerformProxy itself, NSForwarding works in two steps: the first is the “fast path” that’s used to redirect the message to another object — that’s what we want, if the target actually responds to the selector — and the “slow path”, where we can actually play with the NSInvocation.

@implementation PerformProxy

...
// Try to forward to the real target
- (id)forwardingTargetForSelector:(SEL)sel_
{
    if ([_target respondsToSelector:sel_]) {
        return _target;
    } else {
        return self;
    }
}

// Otherwise, handle the message ourselves
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel_ 
{
    NSString * types = [NSString stringWithFormat:@"%s%s%s",_returnType,@encode(id),@encode(SEL)];
    return [NSMethodSignature signatureWithObjCTypes:types.UTF8String];
}

- (void)forwardInvocation:(NSInvocation *)invocation_ 
{
    _returnValueHandler(invocation_); 
}

@end

What about _returnType and _returnValueHandler? They were passed to the PerformProxy at initialization, by the -performIfResponds methods of the NSObject category. Again, it’s only useful in cases where the real target doesn’t respond to the message.

@implementation NSObject (PerformProxy)

// Messages returning `void`: no need to set a fallback return value.
- (instancetype)performIfResponds
{
    return [[PerformProxy alloc] initWithTarget:self returnType:@encode(void)
                             returnValueHandler:^(NSInvocation* invocation_){}];
}

// Messages returning an object: simply set the fallback as the invocation return value.
- (instancetype)performOrReturn:(id)object_
{
    return [[PerformProxy alloc] initWithTarget:self returnType:@encode(id)
                             returnValueHandler:^(NSInvocation* invocation_){
                                 id obj = object_;
                                 [invocation_ setReturnValue:&obj];
                             }];
}

// Messages returning a “value”: use the objcType of the passed NSValue, and “unbox” it into a local buffer to return it.
- (instancetype)performOrReturnValue:(NSValue*)value_
{
    return [[PerformProxy alloc] initWithTarget:self returnType:value_.objCType
                             returnValueHandler:^(NSInvocation* invocation_){
                                 char buf[invocation_.methodSignature.methodReturnLength];
                                 [value_ getValue:&buf];
                                 [invocation_ setReturnValue:&buf];
                             }];
}

@end

That’s all! Easy, right.

Well, OK, That wasn’t exactly trivial. That’s not much code, either. So what does it give us?

The good stuff

Existing patterns become easier to write, and easier to read.

We never use respondsToSelector: anymore: all our delegates, and protocols with @optional methods are sent through performIfResponds, and we don’t worry about it anymore.

Another great usecase is for using recent API while maintaining compatibility with older systems. For example, here’s how we’re dealing with 3D Touch Quick Actions, while maintaining compatibility with iOS8:

UIApplication.sharedApplication.performIfResponds.shortcutItems = /* array of items */;

The -setShortcutItems: message is simply ignored is UIApplication doesn’t handle it. Before that, we used to write abstraction layers for compatibility.
It’s completely useless now.

We’re starting to use new coding patterns.

It’s OK not to check anything: just send the message, and if the receiver can handle it, that’ll work.

In the latest version of Captain Train for iOS, we’ve added a new feature called “Contextual Help”. The context is inferred from tags, and tags come from all kinds of objects: ViewControllers, because the the help isn’t the same in the search form or in the cart; and model objects, because there are specific help articles for e.g. Eurostar or Deutsche Bahn train tickets.

In the end, the easiest method was to simply “ask” objects for their help tags:

for(id obj in contextObjects) {
	tags = [obj performOrReturn:nil].helpTags;
}

Instead of defining categories and superclasses, we just have abstract protocols.

PerformIfResponds allows to specify a default implementation, and this is starting to look very similar to the Protocol-Oriented-Programming from that famous “Grumpy” talk at WWDC 2015.

The not-that-good stuff

Well, there are few drawbacks.

  • Forwarding is slow. In the good scenario, when the target object does respond to the message, it’s 20 times slower than regular method dispatch. The slow path, with NSInvocation, is 100x slower.
  • There’s no type safety. At all. It’s very easy to use the wrong -performOrReturn: variant, and then it’s Game Over. 11

What if we made a new language that made this kind of things easy, fast, and safe?
Oh, wait.

I’ve made the most “Objective-C” thing that I could, using dynamic message resolution, invocations, and other runtime features, and the end result a fundamental feature of Swift.

Mmm.

Source code is on github for your amusement, and I’m on Twitter for comments.

  1. This is the blogpost version of a talk at Cocoaheads Paris in March 2016. Also, it may be my last post ever on Objective-C. Let’s make it count. 

  2. The solution is very “Objective-C” in spirit. I’m disappointed to only find it now. 

  3. See also the official documentation 

  4. … and Bill Bumgarner’s tour of objc_msgSend, from the last decade, which is still largely relevant 

  5. This is the dirty little secret of Objective-C: although we don’t know which method will be actually called until runtime, its return “variant” has to match the version of objc_msgSend that was chosen at compile time. Not so dynamic. 

  6. Selectors are (almost) C string pointers. They’re uniqued at compile time, so they can be compared with ==

  7. it doesn’t call the implementation function, it jumps to it. That’s “tail call optimization”: the called method can return directly to the caller, without passing through objc_msgSend again. That’s also why objc_msgSend doesn’t appear in call stacks. . 

  8. There’s this terrific blogpost reverse-engineering how __forwarding__ exactly works internally. 

  9. By the way, blocks are objects too. And you can send -invoke to an NSBlock. 

  10. Xcode 7.3 brings objc_boxable, which lets you declare boxing support for custom structures

  11. Foundation logs a rather nice error message for this: NSForwarding: warning: method signature and compiler disagree on struct-return-edness of 'someMethod'. Signature thinks it does not return a struct, and compiler thinks it does.