objective-c中的协议有多种,其中正式协议要求显示的采用协议。采用协议的办法是在类的@interface声明中列出协议的名称。采用协议意味着你承诺实现改协议的所有方法。
声明协议的语法看起来与声明类或类别的语法有点像。不过这里使用的是@protocol告诉编译器这是一个新的正式协议。@protocol之后是协议名称,名称必须唯一。然后是一个方法声明列表,协议的每个采用者都必须实现这些方法。
协议声明以@end结束,使用协议不引入新的实例变量。下面是NSCoding协议:
@protocol NSCoding
-(void) encodeWithCoder:(NSCoder*)aCoder;
-(id) initWithCoder:(NSCoder*)aDecoder;
@end
当某个类采用NSCoding协议时,该类则承诺实现这两个方法。encodeWithCoder:方法用于接受对象的实例变量并将其转换为NSCoder类的对象。initWithCoder:方法从NSCoder类的对象中提取经过转换的冻结的实例变量并使用它们初始化一个新对象。这两个方法总是成对实现的。如果你从来不将一个对象转换为另一个新对象,那么对该对象进行编码是毫无疑义的,如果你从不对对象进行编码,那么你也无法创建一个新对象。
要采用某个协议,需要在类的声明中列出该协议的名称,并用尖括号将协议名称括起来。例如,Car类要采用NSCopying协议,则其类声明如下:
@interface Car : NSObject <NSCopying>
{
//instance variables
}
// methods
@end
采用某个协议表示该类的对象可以完成两个非常重要的操作:一是能够对自身进行编码或解码,二是能够复制自身。
要复制一个car对象,需要复制engine和tire对象。
Engine类的新接口如下:
@interface Engine : NSObject <NSCopying>
@end
因为Engine类采用了NSCopying协议,所以必须实现copyWithZone方法。zone是NSZone类的一个对象,指向一块可供分配的内存区域。当你向一个对象发送copy消息时,该copy消息在到达你的代码之前被转换为copyWithZone方法。
Engine类的copyWithZone方法实现如下:
-(id) copyWithZone:(NSZone*)zone
{
Engine*engineCopy;
engineCopy =[[[self class]
allocWithZone:zone]
init];
return(engineCopy);
}
由于Engine类没有实例变量,因此必须创建一个新的engine对象。但是engineCopy对象右边的声明是非常复杂的,消息发送嵌套深度多达3层。
copyWithZone:方法的首要任务是获得self对象所属的类。然后copyWithZone方法向self对象所属的类发送allocWithZone消息,以分配内存并创建一个该类的新对象。最后copyWithZone方法给这个新对象发送init消息以使其初始化。下面讨论为什么要使用复杂的消息嵌套,尤其是[self class]这种用法。
alloc是一个类方法,由于allocWithZone方法的声明是以加号开头的,因此它也是一个类方法:
+(id)allocWithZone:(NSZone*)zone;
需要将该消息发送给一个类,而不是一个实例变量。考虑一下Engine的子类Slant6。如果给一个Slant6类的对象发送copy消息,因为我们最后使用的是Engine类的复制方法,所以这行代码最终在Engine类的copyWithZone方法中结束。若直接给Engine类发送allocWithZone:消息,则将创建一个新的Engine类的对象,而不是Slant6类的对象。如果Slant6类增加一些实例变量,Engine类对象将无法容纳额外的变量,从而导致内存溢出错误。
所以通过[self class],allocWithZone消息将会被发送给正在接收copy消息的对象所属的类,如果self是一个slant6类的对象,那这里将创建一个slant6类的新对象。如果我们的程序在将来添加了一些全新的engine对象(如MatterAntiMatterReactor),则这些新的engine对象也会被正确复制。
allocWithZone方法的最后一行返回新创建的对象。
Tire类有两个实例变量pressure和threadDepth,这两个实例变量需要被复制到Tire类的新对象中,而且AllWeatherRadial子类又引入了两个额外的实例变量,rainHandling和snowHandling,这两个变量也需要被复制到新对象中。首先Tire类遵守协议采用语法的接口如下:
@interface Tire:NSObject<NSCopying>
{
float presure;
float treadDepth;
}
@end
下面是copyWithZone方法的实现:
-(id) copyWithZone:(NSZone*)zone
{
Tire *tireCopy;
tireCopy=[[[self class] allocWithZone:zone]
initWithPressure:pressure
threadDepth:threadDepth];
return (tireCopy);
}
由于创建对象时必须调用init方法,所以将新的tire对象的pressure和threadDepth设置为我们正在复制的tire对象的值。该方法正好是Tire类的指定初始化函数,但并不是必须要使用指定初始化函数来执行复制操作。也可以使用简单的init方法和访问器方法来修改对象的属性。
因为AllWeatherRadial是一个可以复制的类的子类,所以它既不需要实现allocWithZone方法,也不需要使用[self class]形式。AllWeatherRadial类只需要请求其父类执行copy操作,并期望父类正确复制以及在分配对象时使用[self class]技术。因为Tire类的copyWithZone方法使用[self class]来确定要复制的对象所属的类,所以该方法将创建一个AllWeatherRadial类的新对象。
由于要履行对NSCopying协议的承诺,Car类必须实现友元方法copyWithZone。下面是Car类的copyWithZone方法的实现:
-(id) copyWithZone:(NSZone*)zone
{
Car*carCopy;
carCopy = [[[self class]
allocWithzone:zone]
init];
carCopy.name=self.name;
Engine*engineCopy;
engineCopy=[[engine copy]auto release];
carCopy.engine=engineCopy;
int i;
for(i=0;i<4;i++){
Tire*tireCopy;
tireCopy=[[self tireAtIndex:i]copy];
[tireCopy autore lease];
[carCopy setTire:tireCopy
atIndex:i];
}
return (carCopy);
}
首先,通过给正在接收copy消息的对象所属的类发送allocWithZone消息分配一个新的car对象:
Car *carCopy;
carCopy=[[[self class]
allocWithZone:zone]
init];
虽然CarParts-copy项目现在不包含Car类的子类,但还是要通过使用self所属的类分配新对象来保障Car类的未来,需要复制car对象的名称:
carCopy.name=self.name;
name属性复制其字符串对象,因此新的car对象将拥有正确的名称。
接下来复制engine对象,并通知carCopy使用复制的engine对象作为自己的engine属性:
Engine*engineCopy;
engineCopy=[[engine copy]auto release];
carCopy.engine=engineCopy;
engine对象为什么要自动释放?因为通过自动释放engine对象,其保留计数器的值将在未来某个时间自动释放池被销毁时减少1。
我们能够使用简单的[engineCopy release]替代engine对象的自动释放,这样就必须在setEngine方法被调用以后再释放该engineCopy对象。否则engineCopy对象可能在使用之前就被销毁了。
在carCopy保留了新的engine对象以后,copyWithZone方法执行4次for循环,分别复制每个tire对象并将复制的对象安装到新的car中:
int i;
for (i=0;i<4;i++){
Tire*tireCopy;
tireCopy =[[self tireAtIndex:i]copy];
[tireCopy auto release];
[carCopy setTire:tireCopy
atIndex:i];
}
循环中的代码使用访问器方法在每趟循环中先后获得位置0的tire对象,位置1的tire对象,然后这些tire对象被复制并被自动释放,因而它们的内存可以被正确回收。接下黎carCopy被告之使用同一位置的新的tire对象。因为我们已经在Tire类和AllWeatherRadial类中精心构造了copyWithZone方法,所以这段代码可以使用这两个类的tire对象正常工作。
最后是完整的main()函数:
int main (int argc,const char*argv[])
{
NSAuto releasePool*pool;
pool=[[NSAuto releasePool alloc] init];
Car*car =[[Car alloc]init];
car.name =@“Herbie”;
int i;
for (i=0;i<4;i++){
AllWeatherRadial*tire;
tire=[[AllWeatherRadial alloc] init];
[car setTire:tire
atIndex:i];
[tire release];
}
Slant6*engine=[[Slant6 alloc] init];
car.engine =engine;
[engine release];
[car print];
car*carCopy=[car copy];
[carCopy print];
[car release];
[carCopy release];
[pool release];
return (0);
}
该程序在输出原始的car对象以后,复制该car对象并将其输出。
你可以在使用的数据类型中为实例变量和方法参数指定协议名称。在id类型表示一个可以指向任何类型的对象的指针,它是一个泛型对象类型。你可以将任何对象赋值给一个id类型的变量,也可以将一个id类型的变量赋值给任何类型的对象指针。如果一个用尖括号括起来的协议名称跟随在id之后,则编译器讲知道你期望任意类型的对象。例如,NSControl类中有一个名为setObjectValue的方法,该方法要求对象遵守NSCopying协议:
-(void)setObjectValue:(id<NSCopying)obj;
编译器将检查参数类型并提出警告。
objective-C2.0增加类两个新的协议修饰符:@optional和@required。一个BaseballPlayer协议的类有两个要求实现的方法:-drawHugeSalary和
-swingBat ,还有3个不可选择实现的方法slideHome,catchBall 和throwBall
Cocoa中的非正式协议逐渐被带有许多@optional方法正式协议所代替。