正式协议
与非正式协议一样,正式协议是一个命名的方法列表。但与非正式协议不同的是,正式协议要求显式地采用协议。采用协议的办法是在类的@interface声明中列出协议的名称。此时,你的类遵守该协议。采用协议意味着你承诺实现该协议的所有方法。否则,编译器会通过生成警告来提醒你。
正式协议就像Java的接口一样。事实上,Objective-C的协议正是受了Java接口的启发。
声明协议
下面我们看由Cocoa声明的一个协议--NSCopying。如果你采用了NSCopying协议,你的对象将知道如何复制自己:
@protocol NSCopying
- (id) copyWithZone:(NSZone *) zone;
@end
声明协议的语法看起来与声明类或类别的语法有点像。不过这里不是使用@interface,而是使用@protocol告诉编译器:“下面将是一个新的正式协议。”@protocol之后是协议名称,协议名称必须唯一。
然后是一个方法声明列表,协议的每个采用者都必须实现这些方法。协议声明以@end结束。使用协议不引入新的实例变量。
我们来看另一个例子。下面是Cocoa的NSCoding协议:
@protocol NSCoding
-(void) endoceWithCoder:(NSCoder *) aCoder;
- (id) initWithCoder:(NSCoder *) aDecoder;
@end
当某个类采用NSCoding协议时,意味着该类承诺实现这两个方法。encodeWithCoder:方法用于接受对象的实例变量并将其转换为NSCoder类的对象。initWithCoder:方法从NSCoder类的对象中提取经过转换的冻结的(freeze-dried)实例变量并使用它们初始化一个新对象。这两个方法总是成对实现的。如果你从来不将一个对象转换为另一个新对象,那么对该对象进行编码是毫无意义的。如果你从不对对象进行编码,那么你也无法创建一个新对象。
采用协议
要采用某个协议,你可以在类的声明中列出该协议的名称,并用尖括号将协议名称括起来。例如,Car类要采用NSCopying协议,则其类声明像下面这样:
@interface Car:NSObject <NSCopying>{
// instance variables
}
// methods
@end //Car
如果Car类要同时采用NSCopying和NSCoding这两个协议,则类声明如下:
@interface Car:NSObject <NSCopying,NSCoding>{
//instance variables
}
// methods
@end //Car
你可以按任意顺序列出这些协议,没有什么影响。
采用某个协议,相当于给阅读类声明的编程人员发送一条消息,表明该类的对象可以完成两个非常重要的操作:一是能够对自身进行编码或解码,二是能够复制自身。
协议和数据类型
你可以在使用的数据类型中为实例变量和方法参数指定协议名称。这样,你可以经Objective-C的编译器提供更多一点的信息,从而有助于检查你代码中的错误。
id类型表示一个可以指向任何类型的对象的指针,它是一个泛型对象类型。你可以将任何对象赋值给一个id类型的变量,也可以将一个id类型的变量赋值给任何类型的对象指针。如果一个用尖括号括起来的协议名称跟随在id之后,则编译器将知道你期望任意类型的对象,只要其遵守该协议。
Objective-C2.0新特性
Objective-C2.0增加了两个新的协议修饰符:@optional 和 @required 。早期版本的Objective-C要求必须实现该协议的所有方法。Objective-C2.0则不必。
@protocol BaseballPlayer
-(void) drawHugeSalary;
@optional
-(void) slideHome;
-(void) catchBall;
-(void) throwBall;
@required
-(void) swingBat;
@end //BaseballPlayer
因此,一个采用BaseballPlayer协议的类有两个要求实现的方法:-drawHugeSalary和-swingBat,还有3个可选择实现的方法:slideHome,catchBall和throwBall。
非正式协议似乎就可以胜任,为什么 苹果公司还要这样设计呢?这是Cocoa提供的又一利器,可以用来在类声明和方法声明中明确表达我们的意图。如果,你在一个头文件中看到下面这样的代码:
@interface CalRipken:Person <BaseballPlayer>
你可以立即看出我们正在处理BaseballPlayer类的对象。如果使用非正式协议,就无法表达这些信息。同样,你可以使用协议修饰方法的参数:
-(void) draft:(Person<BaseballPlayer>);
这行代码明确指出了应该选拔什么样的人参加棒球比赛。