非正式协议的必要性是什么?

发布于 2024-12-06 18:32:44 字数 396 浏览 0 评论 0原文

我已经阅读了 Apple 文档网站上有关正式和非正式协议的在线文档,但我错过了有关非正式协议的要点。我的意思是,

  1. 一个类不能遵守非正式协议,它默认遵守非正式协议,因为非正式协议几乎总是 NSObject 类的类别。
  2. 如果要实现非正式协议,则必须在接口文件中重新声明要实现的方法。
  3. 另一个类无法检查您是否符合非正式协议(该类必须检查它是否响应某些选择器,但不需要非正式协议也可以完成相同的操作)。

那么,制定非正式协议有什么意义呢?考虑到上述三点,并且考虑到没有它们你也可以做同样的事情,我真的无法理解它们在哪里有用。我确信我错过了一些东西,也许你可以帮忙。

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

I've read the online docs about formal and informal protocols on Apple's documentation site, but I missed the point about informal protocols. I mean,

  1. a class cannot conform to an informal protocol, it conforms to it by default since informal protocols are almost always categories of the NSObject class.
  2. If you want to implement an informal protocol, you must redeclare the methods you want to implement in your interface file.
  3. Another class cannot check if you conform to the informal protocol (the class must check if it responds to some selectors, but the same can be done without the need of an informal protocol).

So, what's the point of having an informal protocol? I can't really understand where they could be useful given the three points above and given that you could do the same things without them. I'm sure I'm missing something, maybe you can help.

EDIT: after a while, I still do not see why informal protocols where used, apart from a logical point of view, i.e. group together some methods. Any idea?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

冰火雁神 2024-12-13 18:32:44

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

另一个原因是编译器和目标平台 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 现在支持正式协议声明中的可选方法,不再需要非正式协议。

You’ve got the grouping together a set of related methods part right. An informal protocol lists which (optional) methods can be implemented if a class needs to ‘conform’ to that informal protocol.

The other reason is the compiler and the target platform ABI. Consider a delegating class whose objects accept a delegate. Internally, methods in the delegating class would do something like:

id delegate;

…

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

Without an informal protocol, the compiler would emit warnings for the excerpt above because it wouldn’t be aware that someMethod:hey:ho: exists, its return type and its parameter types. More importantly, without knowing the method signature, the compiler would have to guess the return type and the argument types in order to prepare the call site, and this guess could very well be a mismatch.

For example, looking at the message being sent in the excerpt above the compiler could guess that the method accepts an integer, an NSString, and another NSString as arguments. But what if the method is originally supposed to accept a floating point number as the first argument? Passing an integer argument (in this case, the argument is stored in a standard register) is different from passing a 64-bit floating point argument (in this case, a SSE register). And what if the method actually supports variable arguments in the last argument instead of a single string? The compiler wouldn’t prepare the call site accordingly, which would lead to crashes.

Generating the assembly of the excerpt above helps illustrate this problem:

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

As you can see, 42 was stored in register EDX.

However, if we add an informal protocol stating that the first parameter is of type float:

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

then the compiler prepares the call site differently:

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

As you can see, 42 was stored in register XMM0.

And if we change the informal protocol so that the last argument is variadic:

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

then the compiler prepares the call site in another manner:

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

Note the movb $0, %al instruction. That’s required by the x86_64 ABI: when calling a variadic function, the caller must store in AL the number of floating-point registers that have been used. In this case, none, hence $0.

In summary, informal protocols, besides grouping related methods, help the compiler identify the signature of a method and correctly prepare the call site before sending a message. However, given that Objective-C now supports optional methods in a formal protocol declaration, informal protocols aren’t needed any longer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文