为变量声明协议的目的是什么?

What is the purpose of declaring a protocol for a variable?

我一直在阅读有关 Objective-C 的协议,但我无法理解:

考虑这一行

Person <CoordinateSupport> *person = [[Person alloc] init];

声明变量符合协议的目的是什么CoordinateSupport?这是编译时的东西吗,所以如果我给 person 分配了不同的东西,Xcode 可以警告我,或者在 运行 时间有什么目的吗?

我看不出变量如何符合协议。好吧,一个 class 很容易看到,因为你可以有一个协议定义你想要一些 class 遵循的方法,但是一个 ivar?

我没看到。

声明一个变量符合协议时的标准模式是给它 "any object" 类型,id。声明一个变量都具有特定类型 都符合协议通常是多余的——我稍后会解释原因。现在,我们来谈谈 id<P> 类型的变量,其中 P 是某种协议,以及它们为何有用。这种类型应该读作 "an instance of any class that conforms to P."

为了使下面的讨论具体化,让我们定义一个协议:

@protocol Adder
- (NSInteger)add:(NSInteger)a to:(NSInteger)b;
@end

I cannot see how a variable can conform to a protocol.

这个很简单。当变量表示实现协议中所有必需方法的 class 实例时,它符合 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 class,您当然可以创建一个新的 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 的类型是 "an instance of any class that conforms to 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 的 class,而在 Adder 协议中没有任何地方我们对名为 beadCount 的方法有什么看法吗?

What is the purpose of declaring the variable to conform to [a protocol]?

目的是为了信息隐藏。当你想要一个符合 Adder 的 class 时,你不需要关心实际的 class 是什么——你只需要得到一个 id<Adder>。假设 Abacus 是一个系统 class,并且您编写了以下代码:

- (Abacus *)getAdder { return [[Abacus alloc] init]; }
- (void)doWork {
    Abacus *a = [self getAdder];
    // Do lots of adding...
}

然后,在 iOS 42 年,Apple 提出了一项新的创新 – Calculator class!您的朋友告诉您,Calculator 将两个数字相加的速度是 Abacus 的两倍多,所有酷孩子都在使用它!您决定重构您的代码,但您意识到您不仅需要更改 getAdder 的 return 类型,而且还需要更改分配给 return 的所有变量的类型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] 即可!一条线。您的其余代码保持完全相同。在这种情况下,您 隐藏了 实例的真实类型 return 来自 getAdder 的其余代码。信息隐藏使重构更容易。

最后,我答应解释为什么像 Abacus <Adder> *a = ... 这样的东西通常是多余的。你在这里说的是“a 是符合 AdderAbacus 的一个实例。”但是您(和编译器)已经知道 Abacus 符合 Adder – 它就在接口声明中!作为rmaddy points out,有些情况下你想谈论一个实例,它要么是一个给定的class,要么是它的一个子class,并且还指定它符合一个协议,但这些情况很少见,而且通常不需要同时指定 class 和协议一致性。

有关详细信息,请查看 Apple 的 Working with Protcols 指南。