非正式协议的需求是什么?

时间:2011-09-26 21:36:57

标签: objective-c protocols categories

我在Apple的文档网站上阅读了关于正式和非正式协议的在线文档,但我忽略了关于非正式协议的观点。我的意思是,

  1. 一个类不能符合非正式协议,它默认符合它,因为非正式协议几乎总是NSObject类的类别。
  2. 如果要实现非正式协议,则必须重新声明要在接口文件中实现的方法。
  3. 另一个类无法检查您是否符合非正式协议(该类必须检查它是否响应某些选择器,但可以在不需要非正式协议的情况下完成相同的操作)。
  4. 那么,拥有非正式协议有什么意义呢?鉴于上述三点并且没有它们你可以做同样的事情,我无法真正理解它们在哪里有用。我确定我错过了一些东西,也许你可以帮忙。

    编辑:过了一段时间,我仍然不明白为什么使用非正式协议,除了逻辑观点,即将一些方法组合在一起。有什么想法吗?

1 个答案:

答案 0 :(得分:4)

您已将组合在一起,将一组相关方法组合在一起。非正式协议列出了如果某个类需要“符合”该非正式协议,则可以实现哪些(可选)方法。

另一个原因是编译器和目标平台ABI。考虑一个委托类,其对象接受委托。在内部,委托类中的方法可以执行以下操作:

id delegate;

…

if ([_delegate respondsToSelector:@selector(someMethod:hey:ho:)]) {
    [_delegate someMethod:42 hey:@"hey" ho:@"ho, let's go"];
}

如果没有非正式协议,编译器会为上面的摘录发出警告,因为它不会意识到someMethod:hey:ho:存在,它的返回类型及其参数类型。更重要的是,在不知道方法签名的情况下,编译器必须猜测返回类型和参数类型以准备调用站点,这种猜测很可能是不匹配。

例如,查看上面摘录中发送的消息,编译器可以猜测该方法接受整数,NSString和另一个NSString作为参数。但是如果该方法最初应该接受浮点数作为第一个参数呢?传递整数参数(在本例中,参数存储在标准寄存器中)与传递64位浮点参数(在本例中为SSE寄存器)不同。如果该方法实际上支持最后一个参数中的变量参数而不是单个字符串,该怎么办?编译器不会相应地准备调用站点,这会导致崩溃。

生成上述摘录的程序集有助于说明此问题:

movl    $42, %edx
leaq    L__unnamed_cfstring_(%rip), %rax
leaq    L__unnamed_cfstring_3(%rip), %rcx
movq    -16(%rbp), %rdi
movq    L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq    %rcx, -24(%rbp)
movq    %rax, %rcx
movq    -24(%rbp), %r8
callq   _objc_msgSend

如您所见,42存储在寄存器EDX中。

但是,如果我们添加一个非正式协议,声明第一个参数的类型为float

@interface NSObject (DelegateInformalProtocol)
- (id)someMethod:(float)number hey:(id)hey ho:(id)ho;
@end

然后编译器以不同的方式准备调用站点:

movabsq $42, %rax
cvtsi2ssq   %rax, %xmm0
leaq    L__unnamed_cfstring_(%rip), %rax
leaq    L__unnamed_cfstring_3(%rip), %rcx
movq    -16(%rbp), %rdi
movq    L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq    %rax, %rdx
callq   _objc_msgSend

如您所见,42存储在寄存器XMM0中。

如果我们更改非正式协议,以便最后一个参数是可变参数:

@interface NSObject (DelegateInformalProtocol)
- (id)someMethod:(int)number hey:(id)hey ho:(id)ho, ...;
@end

然后编译器以另一种方式准备呼叫站点:

movl    $42, %edx
leaq    L__unnamed_cfstring_(%rip), %rax
leaq    L__unnamed_cfstring_3(%rip), %rcx
movq    -16(%rbp), %rdi
movq    L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq    %rcx, -24(%rbp)         ## 8-byte Spill
movq    %rax, %rcx
movq    -24(%rbp), %r8          ## 8-byte Reload
movb    $0, %al
callq   _objc_msgSend

请注意movb $0, %al指令。这是x86_64 ABI所要求的:当调用可变参数函数时,调用者必须在AL中存储已使用的浮点寄存器的数量。在这种情况下,没有,因此$0

总之,除了分组相关方法之外,非正式协议还有助于编译器识别方法的签名,并在发送消息之前正确准备呼叫站点。但是,鉴于Objective-C现在支持正式协议声明中的可选方法,不再需要非正式协议。