2

I am working on this piece of code, basically adding a block to NSObject:

class_addMethod(object_getClass([NSObject class]), @selector(toUpper2:), imp_implementationWithBlock(^NSString*(id self, SEL _cmd, NSString* s) {
        NSLog(@"self: %@", self);
        NSLog(@"_cmd: %@", _cmd); // I know %@ is not SEL, but look for yourself
        NSLog(@"s: %@", s);
        return [s uppercaseString];
    }), "@@:@"); // the type signature was created using @encode

To me, this looks fairly innocent, but if I do this: (I've defined a +toUpper2 in a different class too, so that the compiler does not complain):

[(id)[NSObject class] toUpper2:@"hallo"];

This happens:

2018-07-06 16:45:52.302676+0200 xctest[43736:32056962] self: NSObject
2018-07-06 16:45:52.302706+0200 xctest[43736:32056962] _cmd: hallo
2018-07-06 16:45:52.302721+0200 xctest[43736:32056962] s: (null)

As you can see, the arguments got messed up. What's more, if i enact the same method using performSelector, like that:

[NSObject performSelector:@selector(toUpper2:) withObject:@"hallo"];

Then, things get even more astray:

2018-07-06 16:45:52.302737+0200 xctest[43736:32056962] self: NSObject
2018-07-06 16:45:52.302751+0200 xctest[43736:32056962] _cmd: hallo
2018-07-06 16:45:52.302763+0200 xctest[43736:32056962] s: hallo

Can anyone explain this behaviour?

Best regards, thejack

2 Answers 2

2

The docs are wrong, bug filed (41908695). The objc header file is correct:

/** 
 * Creates a pointer to a function that will call the block
 * when the method is called.
 * 
 * @param block The block that implements this method. Its signature should
 *  be: method_return_type ^(id self, method_args...). 
 *  The selector is not available as a parameter to this block.
 *  The block is copied with \c Block_copy().
 * 
 * @return The IMP that calls this block. Must be disposed of with
 *  \c imp_removeBlock.
 */
OBJC_EXPORT IMP _Nonnull
imp_implementationWithBlock(id _Nonnull block)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);

The reason why it was implemented this way was speed speed speed. And simplicity.

Specifically, a method decomposes into a C function that always takes at least two arguments; self and _cmd, both of which happen to pointers. That is followed by 0...N arbitrary arguments which will be packed into registers or onto the stack arbitrarily as defined by the targeted architectures ABI.

A block call site, on the other hand, always decomposes into a C function call where the function has one guaranteed argument; a reference to the block. It is this reference through which the compiler can emit code to reference captured state, if any. Like a method, the block's args are followed by an arbitrary list of arguments.

Now, re-encoding argument lists on any architecture is a nightmare. Slow, prone to error, and extremely complex.

To avoid that, imp_implementationWithBlock() does some behind the scenes magic that returns a function pointer that, when invoked, treats the first argument like a pointer (should be self) into the second argument's slot (overwrites _cmd), shoves the block reference into the first argument's slot, and then tail calls the block's code.

The block doesn't know that it was invoked as a method. And the objc runtime doesn't know that a method invocation trampolined thru to a block.

Sign up to request clarification or add additional context in comments.

Comments

1

I haven't used this method myself, but it looks to me like you've misunderstood the API here.

The docs describe the structure of the block parameter.

block
The block that implements this method. The signature of block should be method_return_type ^(id self, self, method_args …). The selector of the method is not available to block. block is copied with Block_copy().

The emphasis here is mine, but should make it quite clear that your SEL argument is not appropriate here. I'm not entirely sure why they've duplicated self in the description, especially given that you're typically seeing your method_args begin at that parameter index.

1 Comment

That documentation is wrong and I like the incomplete sentence in the disposal section.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.