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.