Зачем нужны неофициальные протоколы? - PullRequest
2 голосов
/ 27 сентября 2011

Я читал онлайн-документы о формальных и неформальных протоколах на сайте документации Apple, но я упустил вопрос о неформальных протоколах.Я имею в виду,

  1. класс не может соответствовать неформальному протоколу, он соответствует ему по умолчанию, поскольку неформальные протоколы почти всегда являются категориями класса NSObject.
  2. Если вы хотите реализовать неформальный протокол, вы должны повторно объявить методы, которые вы хотите реализовать, в своем файле интерфейса.
  3. Другой класс не может проверить, соответствуете ли вы неформальному протоколу (класс должен проверить, отвечает ли он на некоторые селекторы, но то же самое можно сделать без необходимости использования неформального протокола).

Итак, какой смысл иметь неофициальный протокол?Я не могу понять, где они могут быть полезны, учитывая три пункта выше и учитывая, что вы можете делать то же самое без них.Я уверен, что что-то упустил, может быть, вы можете помочь.

РЕДАКТИРОВАТЬ: через некоторое время я все еще не понимаю, почему неформальные протоколы, где используются, кроме логической точки зрения, то есть сгруппировать некоторыеметоды.Есть идеи?

1 Ответ

4 голосов
/ 18 октября 2011

У вас есть , группирующий набор связанных методов справа.Неформальный протокол указывает, какие (необязательные) методы могут быть реализованы, если класс должен «соответствовать» этому неформальному протоколу.

Другая причина - это компилятор и целевая платформа 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 теперь поддерживает необязательные методы в формальном объявлении протокола, неофициальные протоколы больше не нужны.

...