Какова цель объявления протокола для переменной?
Я читал о протоколах на Objective-C, но я не могу понять это:
Рассмотрим эту строку
Person <CoordinateSupport> *person = [[Person alloc] init];
Какова цель объявления переменной для соответствия протоколу CoordinateSupport
? Это что-то только для времени компиляции, поэтому XCode может предупредить меня, если я назначу что-то другое person
или есть какая-то цель во время выполнения?
Я не вижу, как переменная может соответствовать протоколу. Хорошо, класс легко увидеть, потому что у вас может быть протокол, определяющий методы, которым вы хотите, чтобы какой-то класс следовал, кроме ивара?
Я не вижу этого.
1 ответ
Стандартный шаблон при объявлении о том, что переменная соответствует протоколу, состоит в присвоении ей типа "любой объект", id
, Объявление о том, что переменная имеет определенный тип и соответствует протоколу, как правило, является избыточным - я объясню почему позже. А пока давайте поговорим о переменных типа id<P>
, где P
какой-то протокол, и почему они полезны. Этот тип должен читаться как "экземпляр любого класса, который соответствует P
".
Чтобы конкретизировать последующее обсуждение, давайте определим протокол:
@protocol Adder
- (NSInteger)add:(NSInteger)a to:(NSInteger)b;
@end
Я не вижу, как переменная может соответствовать протоколу.
Это легко. Переменная соответствует протоколу Objective-C, когда она представляет экземпляр класса, который реализует все необходимые методы в протоколе.
@interface Abacus : NSObject <Adder>
@end
@implementation Abacus
- (NSInteger)add:(NSInteger)a to:(NSInteger)b { return a + b; }
- (NSInteger)beadCount { return 91; }
@end
Учитывая это Abacus
класс, вы могли бы, конечно, создать новый Abacus
:
Abacus *a = [[Abacus alloc] init];
NSLog(@"%ld", (long)[a add:5 to:6]); // 11
NSLog(@"%ld", (long)[a beadCount]); // 91
Но вы также можете объявить a
просто быть типом id<Adder
, Помните, это означает, что тип a
это "экземпляр любого класса, который соответствует Adder
".
id<Adder> a = [[Abacus alloc] init];
NSLog(@"%ld", (long)[a add:5 to:6]); // 11
NSLog(@"%ld", (long)[a beadCount]); // Compile error: No known instance method for selector 'beadCount'
Компилятор жалуется, потому что все, что мы сказали о типе a
является то, что это класс, который соответствует Adder
и нигде в Adder
Протокол мы говорим что-нибудь о методе с именем beadCount
,
Какова цель объявления переменной для соответствия [протоколу]?
Цель для сокрытия информации. Когда вы хотите класс, который соответствует Adder
вам не нужно заботиться о том, что на самом деле класс - вы просто получите id<Adder>
, Представь это Abacus
системный класс, и вы написали следующий код:
- (Abacus *)getAdder { return [[Abacus alloc] init]; }
- (void)doWork {
Abacus *a = [self getAdder];
// Do lots of adding...
}
Затем в iOS 42 Apple предлагает новую инновацию - Calculator
учебный класс! Твои друзья говорят тебе, что Calculator
складывает два числа вдвое быстрее, чем Abacus
И все классные дети используют это! Вы решаете реорганизовать свой код, но понимаете, что вам нужно не только изменить тип возвращаемого значения getAdder
, но также типы всех переменных, которым вы присваиваете возвращаемое значение getAdder
! Ламе. Что делать, если вы сделали это вместо этого:
- (id<Adder>)getAdder { return [[Abacus alloc] init]; }
- (void)doWork {
id<Adder> *a = [self getAdder];
// Do lots of adding...
}
Теперь, когда вы хотите перейти на Calculator
нужно просто поменять тело getAdder
в return [[Calculator alloc] init]
и вы сделали! Одна линия. Остальная часть вашего кода остается точно такой же. В этом случае вы скрыли истинный тип экземпляра, возвращенного из getAdder
из остальной части вашего кода. Сокрытие информации облегчает рефакторинг.
Наконец, я обещал объяснить, почему что-то вроде Abacus <Adder> *a = ...
обычно избыточно. То, что вы говорите, здесь " a
это пример Abacus
что соответствует Adder
"Но вы (и компилятор) уже знаете, что Abacus
соответствует Adder
- это прямо в объявлении интерфейса! Как указывает rmaddy, в некоторых случаях вы хотите поговорить об экземпляре, который является либо данным классом, либо его подклассом, а также указать, что он соответствует протоколу, но такие ситуации встречаются редко, и чаще всего указываются оба соответствие класса и протокола не требуется.
Для получения дополнительной информации ознакомьтесь с руководством Apple по работе с протоколами.