18.3 实现<NSCopying>协议
如果尝试使用自己类(例如,地址簿)中的copy方法,如下所示:
NewBook=[myBook mutableCopy];
将会收到一条出错消息,它可能如下所示:
*-[AddressBook copyWithZone:]:selector not recognized
*Uncaught exception:
*-[AddressBook copyWithZone:]:selector not recognized
正如注释的那样,要实现使用自己的类进行复制,必须根据<NSCoping>协议实现其中一两个方法。
我们将展示如何为Fraction类添加copy方法,这个类在第一部分“Objective-C 2.0语言”中广泛地使用。注意,这里描述的复制策略的技巧非常适用于你自己的类。如果这些类是任何Foundation类的子类,那么可能需要实现较为复杂的复制策略。必须考虑这样一个事实:超类可能已经实现了它自己的复制策略。
还记得Fraction类包含两个整型实例变量,分别名为numberator和denominator。要产生其中一个对象的副本,需要分配一个新分数的空间并简单地将这两个整数的值复制到新分数中。
实现<NSCopying>协议时,类必须实现copyWithZone:方法来响应copy消息。(这条copy消息仅将一条带有nil参数的copyWithZone:消息发送给你的类)。注意,如果想要区分可变副本和不可变副本,还需要根据<NSMutableCoping>协议实现mutableCopyWithZone:方法。如果两个方法都实现,那么copyWithZone:应该返回不可变副本,而mutableCopytWithZone:将返回可变副本。产生对象的可变副本并不要求被复制的对象本身也是可变的(反之亦然);想要产生不可变对象的可变副本是很合理的(例如,考虑字符串对象)。
@interface指令应该如下所示:
@interface Fraction:NSObject<NSCopying>
Fraction是NSObject的子类,并且符合NSCopying协议。在实现文件Fracion.m中,为新方法添加下列定义:
-(id)copyWithZone:(NSZone*)zone
{
Fraction*newFract=[[Fraction allocWithZone:zone]init];
[newFract setTo:numerator over:denominator];
return newFract;
}
参数zone与不同的存储区有关,你可以在程序中分配并使用这些存储区。只有在编写要分配大量内存的应用程序并且想要通过将空间分配分组到这些存储区中来优化内存分配时,才需要处理这些zone。可以使用传递给copyWithZone:的值,并将它传给名为allocWithZone:的内存分配方法。这个方法在指定存储区中分配内存。
分配新的Fraction对象之后,将接收者的numerator和denominator变量复制到其中。copyWithZone:方法应该返回对象的新副本,这个对象就是在你的方法中实现的。
代码清单18-3测试了你的新方法。
代码清单18-3
//Copying fractions
import“Fraction.h”
import<Foundation/NSAutoreleasePool.h>
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
Fraction*f1=[[Fraction alloc]init];
Fraction*f2;
[f1 setTo:2 over:5];
f2=[f1 copy];
[f2 setTo:1 over:3];
[f1 print];
[f2 print];
[f1 release];
[f2 release];
[pool drain];
return 0;
}
代码清单18-3输出
2/5
1/3
该程序创建了一个名为f1的Fraction对象并将其设置为2/5。然后,它调用copy方法来产生副本,copy方法向你的对象发送copyWithZone:消息。这个方法产生了一个新Fraction,将f1的值复制到其中,并返回结果。回到main函数中,你将这个结果赋给f2。随即,将f2中的值设置为分数1/3,这样就验证了这些操作对原始分数f1没有影响。将程序中的这一行
f2=[f1 copy];
简单地改成
f2=f1;
并在程序结尾删除f2的release语句,观察获得的不同结果。
如果你的类可以产生子类,那么copyWithZone:方法将被继承。在这种情况下,该方法中的程序行
Fraction*newFract=[[Fraction allocWithZone:zone]init];
应该改为
Fraction*newFract=[[[self class]allocWithZone:zone]init];
这样,可以从该类分配一个新对象,而这个类是copy的接收者(例如,如果它产生了一个名为NewFraction的子类,那么应该确保在继承的方法中分配了新的NewFraction对象,而不是Fraction对象)。
如果编写一个类的copyWithZone:方法,而该类的超类也实现了<NSCopying>协议,那么应该先调用超类的copy方法以复制继承来的实例变量,然后加入自己的代码以复制想要添加到该类中的任何附加的实例变量(如果有的话)。
你必须确定是否在类中实现浅复制或深复制,并为其编写文档,以告知类的其他使用者。