声明变量符合协议时的标准模式是给它“任何对象”类型,id
. 声明一个变量既具有特定类型又符合协议通常是多余的——我稍后会解释原因。现在,让我们谈谈 type 的变量,一些协议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
只是 type id<Adder
。请记住,这意味着 of 的类型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
,只需更改 to 的主体getAdder
即可return [[Calculator alloc] init]
!一条线。您的其余代码保持完全相同。在这种情况下,您隐藏getAdder
了从其余代码返回的实例的真实类型。信息隐藏使重构更容易。
最后,我答应解释为什么类似的东西Abacus <Adder> *a = ...
通常是多余的。你在这里说的是“是符合a
的实例。” 但是您(和编译器)已经知道符合- 它就在接口声明中!正如rmaddy 指出的那样,在某些情况下,您想要谈论一个实例,它要么是给定的类,要么是其子类,并且还要指定它符合协议,但这种情况很少见,并且通常同时指定两者不需要类和协议一致性。Abacus
Adder
Abacus
Adder
有关更多信息,请查看 Apple 的使用 Protcols指南。