18.2 浅复制与深复制

代码清单18-1使用不可变字符串来填充dataArray的元素(回忆一下,常量字符串对象是不可变的)。在代码清单18-2中,将使用可变字符串代替它来填充数组,这样就可以改变数组中的一个字符串。观察代码清单18-2,看自己能否理解程序的输出结果。

代码清单18-2


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:

[NSMutableString stringWithString:@“one”],

[NSMutableString stringWithString:@“two”],

[NSMutableString stringWithString:@“three”],

nil

];

NSMutableArray*dataArray2;

NSMutableString*mStr;

NSLog(@“dataArray:);

for(NSString*elem in dataArray)

NSLog(@,elem);

//make a copy, then change one of the strings

dataArray2=[dataArray mutableCopy];

mStr=[dataArray objectAtIndex:0];

[mStr appendString:@“ONE”];

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-2输出


dataArray:

one

two

three

dataArray:

oneONE

two

three

dataArray:

oneONE

two

three


使用下面的语句检索dataArray的第一个元素:


mStr=[dataArray objectAtIndex:0];


然后,使用下面的语句将字符串@“ONE”附加到给这个元素:


[mStr appendString:@“ONE”];


注意,原始数组及其副本中第一个元素的值:它们都被修改了。或许你能理解为什么dataArray的第一个元素发生改变,但不明白为什么它的副本也会改变。从集合中获取元素时,就得到了这个元素的一个新引用,但并不是一个新副本。所以,对dataArray调用objectAtIndex:方法时,返回的对象与dataArray中的第一个元素都指向内存中的同一个对象。随后,修改string对象mStr的副作用就是同时改变了dataArray的第一个元素,从输出结果中可以看到。

但你制作的副本怎么样了?为什么它的第一个元素也会改变?这与默认的浅复制方式有关。它意味着使用mutableCopy方法复制数组时,在内存中为新的数组对象分配了空间,并且将单个元素复制到新数组中。然而将原始数组中每个元素复制到新位置意味着:仅将引用从一个数组元素复制到另一个数组元素。这样做的最终结果,就是这两个数组中的元素都指向内存中的同一个字符串。这与将一个对象赋值给另一个对象没什么不同,就像我们本章开始提到的一样。

要为数组中每个元素创建完全不同的副本,需要执行所谓的深复制。这就意味着要创建数组中的每个对象内容的副本,而不仅是这些对象的引用的副本(并且考虑一下,如果一个数组中的元素本身是数组对象时,深复制意味着该如何处理)。然而使用Foundation类的copy或mutableCopy方法时,深复制并不是默认执行的。在第19章“归档”中,我们将向你展示如何使用Foundation的归档功能来创建对象的深复制。

例如,复制一个数组、字典或集时,会获得这些集合的新副本。然而,如果想要更改其中一个集合而不是它的副本,那么可能需要为单个元素创建自己的副本。例如,假设想要更改代码清单18-2中dataArray2的第一个元素,但不更改dataArray的第一个元素,可以创建一个新字符串(使用stringWithString:之类的方法)并将它存储到dataArray2的第一个位置,如下所示:


mStr=[NSMutableString stringWithString:[dataArray2 objectAtIndex:0]];


然后,可以更改mStr,并使用replaceObject:atIndex:withObject:方法将它添加到数组中,如下所示:</p>


[mStr appendString@“ONE”];

[dataArray2 replaceObjectAtIndex:0 withObject:mStr];


如果顺利的话,你会发现即使替换了数组中的对象之后,mStr和dataArray2的第一个元素仍指向内存中的同一个对象。这意味着随后在程序中对mStr做的修改也将更改数组的第一个元素。如果这不是你想要的,则可以总是释放m S t r,并分配新实例,因为对象会被replaceObject:atIndex:withObject:方法自动保持。