У вас есть , группирующий набор связанных методов справа.Неформальный протокол указывает, какие (необязательные) методы могут быть реализованы, если класс должен «соответствовать» этому неформальному протоколу.
Другая причина - это компилятор и целевая платформа 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: при вызове функции с переменным числом вызывающая сторона должна хранить в AL количество используемых регистров с плавающей запятой.В этом случае нет ни одного, следовательно, $0
.
Таким образом, неофициальные протоколы, помимо группировки связанных методов, помогают компилятору идентифицировать сигнатуру метода и правильно подготовить сайт вызова перед отправкой сообщения.Однако, учитывая, что Objective-C теперь поддерживает необязательные методы в формальном объявлении протокола, неофициальные протоколы больше не нужны.