第18章 复制对象

本章将讨论复制对象相关的一些细节。我们将介绍浅复制和深复制的概念,并讨论如何在Foundation框架下实现对象复制。

在第8章“继承”中,我们讨论了使用简单的赋值语句将对象赋值给另一个对象时发生的情况,比如:


origin=pt;


在这个例子中,origin和pt都是XYPoint对象,其定义如下:


@interface XYPoint:NSObject

{

int x;

int y;

};

……

@end


还记得,这样赋值的结果仅仅是将对象pt的地址复制到origin中。在赋值操作结束时,两个变量都指向内存中的同一个地址。使用一条消息对实例变量进行修改,如:


[origin setX:100 andY:200];


改变了origin和pt变量都引用的XYPoint对象的x、y坐标,因为它们都引用内存中的同一个对象。

这同样适用于Foundation对象:将一个变量赋值给另一个对象仅仅创建另一个对这个对象的引用(但这并不像第17章“内存管理”中所讨论的,将增加引用计数)。所以,如果dataArray和dataArray2都是NSMutableArray对象,那么语句


dataArray2=dataArray;

[dataArray2 removeObjectAtIndex:0];


将从这两个变量引用的同一个数组中删除第一个元素。

18.1 copy和mutableCopy方法

Foundation类实现了名为copy和mutableCopy的方法,可以使用这些方法创建对象的副本。通过实现一个符合<NSCopying>协议(用于制作副本)的方法来完成此任务。如果类必须区分要产生对象的是可变副本还是不可变副本,还需要根据<NSMutableCopying>协议实现一个方法。本章后面将学习如何实现上述方法。

回顾Foundation类的copy方法,给定前面描述的两个NSMutableArray对象dataArray2和dataArray,语句


dataArray2=[dataArray mutableCopy];


在内存中创建了一个新的dataArray副本,并复制了它的所有元素。随后,执行语句


[dataArray2 removeObjectAtIndex:0];


删除了dataArray2中的第一个元素,但却不删除dataArray中的。代码清单18-1说明了这种情况。

代码清单18-1


import<Foundation/NSObject.h>

import<Foundation/NSArray.h>

import<Foundation/NSString.h>

import<Foundation/NSAutoreleasePool.h>

int main(int argc, char*argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];

NSMutableArray*dataArray=[NSMutableArray arrayWithObjects:

@“one”,@“two”,@“three”,@“four”,nil];

NSMutableArray*dataArray2;

//simple assignment

dataArray2=dataArray;

[dataArray2 removeObjectAtIndex:0];

NSLog(@“dataArray:);

for(NSString*elem in dataArray)

NSLog(@,elem);

NSLog(@“dataArray2:);

for(NSString*elem in dataArray2)

NSLog(@,elem);

//try a Copy, then remove the first element from the copy

dataArray2=[dataArray mutableCopy];

[dataArray2 removeObjectAtIndex:0];

NSLog(@“dataArray:);

for(NSString*elem in dataArray)

NSLog(@,elem);

NSLog(@“dataArray2:);

for(NSString*elem in dataArray2)

NSLog(@,elem);

[dataArray2 release];

[pool drain];

return 0;

}


代码清单18-1输出


dataArray:

two

three

four

dataArray2:

two

three

four

dataArray:

two

three

four

dataArray2:

three

four


这个程序定义了可变数组对象dataArray,并分别将其元素设置为字符串对象@“one”、@“two”、@“three”和@“four”。

前面讨论过,赋值语句


dataArray2=dataArray;


仅仅创建了对内存中同一数组对象的另一个引用。从dataArray2中删除第一个对象并随后输出两个数组对象中的元素,不出意料,这两个引用中的第一个元素(字符串@“one”)都会消失。

接下来,创建一个dataArray的可变副本并将它赋值给dataArray2的最终副本。这就在内存中创建了两个截然不同的可变数组,两者都包含三个元素。现在,删除dataArray2中的第一个元素时,不会对dataArray的内容有任何影响,正如程序输出的最后两行验证的一样。

注意,产生一个对象的可变副本并不要求被复制的对象本身是可变的。这种情况同样适用于不可变副本:可以创建可变对象的不可变副本。

还要注意在产生数组的副本时,数组中每个元素的保持计数将通过复制操作自动增1。因此,如果产生数组的副本并随即释放原始数组,那么副本仍然包含有效的元素。

因为dataArray的副本是在程序中使用mutableCopy方法产生的,所以你要负责释放它的内存。上一章讲述了你负责释放自己使用其中一个copy方法创建的对象这条规则。这解释了下面这个程序行


[dataArray2 release];


的实质,它位于代码清单18-1的结尾。