目标C向类参数不匹配添加方法

时间:2018-07-06 15:03:14

标签: objective-c objective-c++

我正在处理这段代码,基本上是在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

对我来说,这看起来很无辜,但是如果我这样做:(我也在另一个类中定义了+ toUpper2,以便编译器不会抱怨):

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

发生这种情况:

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)

如您所见,参数陷入混乱。而且,如果我使用performSelector实施相同的方法,例如:

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

然后,事情变得更加误入歧途:

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

任何人都可以解释这种行为吗?

最好的问候, 劫机者

2 个答案:

答案 0 :(得分:2)

文档是错误的,已提交错误(41908695)。 objc头文件正确:

/** 
 * 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);

之所以采用这种方式,是因为速度,速度,速度。和简单。

具体来说,一种方法分解为一个C函数,该函数始终(总是)至少使用两个参数作为参数。 self_cmd,它们都发生在指针上。随后是0 ... N个任意参数,这些参数将按目标体系结构ABI的定义任意打包到寄存器或堆栈中。

另一方面,

块调用站点总是分解为C函数调用,其中该函数具有一个保证的参数;对块的引用。通过该引用,编译器可以发出代码以引用捕获的状态(如果有)。像方法一样,该块的args后跟一个任意参数列表。

现在,在任何体系结构上重新编码参数列表都是一场噩梦。速度慢,容易出错,并且极其复杂。

为避免这种情况,imp_implementationWithBlock()在幕后做了一些魔术,该魔术返回了一个函数指针,该函数指针在被调用时将第一个参数像指针(应为self)处理为第二个参数自变量的插槽(覆盖_cmd,将块引用推入第一个自变量的插槽,然后尾部调用该块的代码。

该块不知道它是作为方法调用的。而且objc运行时不知道方法调用已遍历一个块。

答案 1 :(得分:1)

我自己没有使用过此方法,但在我看来,您似乎在这里误解了API。

docs描述了block参数的结构。

  


  实现此方法的块。块的签名应为method_return_type ^(id self,self,method_args…)。 该方法的选择器不可阻止。用Block_copy()复制块。

这里的重点是我的,但是应该很清楚地说明您的SEL参数在这里不合适。我不确定是为什么它们在说明中重复了self,特别是考虑到您通常会看到method_args从该参数索引处开始。