18.4 用赋值方法和取值方法复制对象

只要实现赋值方法或取值方法,都应该考虑实例变量中存储的内容、要检索的内容以及是否需要保护这些值。例如,考虑使用setName:方法来设置AddressCard对象的名称时:


[newCard setName:newName];


假设newName是一个字符串对象,它包含新地址卡片的名称。假定在赋值方法例程内,你只是简单地将参数赋值给相应的实例变量:


-(void)setName:(NSString*)theName

{

name=theName;

}


现在,如果随后更改了程序的newName中包含的一些字符,那么你认为会发生什么?对,这样也会无意间改变地址卡片中对应的域,因为它们都引用相同的string对象。

前面已经学过,为了避免无意间对程序产生影响,比较安全的途径是在赋值方法例程中产生对象的副本。通过使用alloc方法来创建新的string对象,然后使用initWithString:函数将该方法提供的参数值赋给它,可以实现在赋值对象中产生副本。

还可以编写如下的setName:方法来使用copy:


-(void)setName:(NSString*)theName

{

name=[theName copy];

}


当然,要使赋值方法例程的内存管理友好一些,首先应该自动释放旧的值,如下所示:


-(void)setName:(NSString*)theName

{

[name autorelease];

name=[theName copy];

}


如果在属性声明中为实例变量指定了copy属性,那么合并后的方法会使用类的copy方法(你编写的copy方法或继承的copy方法)。所以下列property声明


@property(nonatomic, copy)NSString*name;


会生成一个合并的方法,其行为类似于


-(void)setName:(NSString*)theName

{

if(theName!=name){

[name release]

name=[theName copy];

}

}


此处使用nonatomic是为了告诉系统不要使用mutex(互斥)锁定保护属性存取器方法。编写线程安全代码的人会使用mutex锁定来防止同一代码中的两个线程同时执行,如果同时执行了,会导致可怕的问题。然而这种锁定也会让程序变慢,如果知道这个代码只会在单线程中运行,就可避免使用这种锁定方法。

如果未指定nonatomic或者指定了atomic(这是默认值),那么会使用mutex锁定保护实例变量。另外,合并的取值方法将被保持,并在实例变量的值返回前自动释放该实例变量。在没有使用垃圾回收的环境中,这可保护实例变量不会被赋值方法调用所重写,这种赋值方法在设置新值前会释放实例变量的旧值。在取值方法中的这种保持确保了旧的值不会被销毁。

注意虽然保持/自动释放问题与垃圾回收环境无关(还记得这些方法调用都会被忽略),然而Mutex锁定问题不是这样。因此,如果代码在多线程环境中运行时,要考虑使用atomic存取器方法。

关于保护实例变量值的讨论同样适用于取值例程。如果返回对象,那么必须确保对返回值的更改不影响你的实例变量的值。在这样的情况下,可以生成实例变量的副本,并将它用作返回值。

回到copy方法的实现,如果正在复制的实例变量包含不可变的对象(如,不可变的string对象),那么可能不需要生成这个对象内容的新副本。仅通保持该对象来生成它的新引用,可能就足够了。例如,为AddressCard类实现copy方法时,这个类包含name和email成员,下面实现的copyWithZone:方法就足够了:


-(AddresssCard)copyWithZone:(NSZone)zone

{

AddressCard*newCard=[[AddressCard allocWithZone:zone]init];

[newCard retainName:name andEmail:email];

return newCard;

}

-(void)retainName:(NSString)theName andEmail:(NSString)theEmail

{

name=[theName retain];

email=[theEmail retain];

}


这里没有使用setName:andEmail:方法来复制实例变量,因为这个方法生成了它的参数的新副本,这将违反整个练习的目的。可替换为,只需使用一个称为retainName:andEmail:的新方法保持两个变量(可以直接在copyWithZone:方法中设置newCard中的两个实例变量,然而这涉及指针操作,而目前我们要避免使用指针。当然指针操作可能效率更高,并且不会将方法[retainName:andEmail:]暴露给类的用户,这个[retainName:andEmail:]方法原义并不是public使用的,因此有些时候,可能需要学习如何这样使用它——但不是现在!)

认识到这里可以侥幸成功地保持实例变量(而不是产生它们的完整副本),因为被复制的地址卡的所有者不能影响原始对象的name和email成员。可能想要仔细地想一下,以验证这个例子(提示:这与赋值方法有关)。