CopyWithZone:关于深拷贝/浅拷贝

Copying Collections

 

There are two kinds of object copying: shallow copies and deep copies. The normal copy is a shallow copy that produces a new collection that shares ownership of the objects with the original. Deep copies create new objects from the originals and add those to the new collection. This difference is illustrated by Figure 1.

Figure 1   Shallow copies and deep copies

Shallow Copies

There are a number of ways to make a shallow copy of a collection. When you create a shallow copy, the objects in the original collection are sent a retain message and the pointers are copied to the new collection. Listing 1 shows some of the ways to create a new collection using a shallow copy.

Listing 1  Making a shallow copy

NSArray *shallowCopyArray=[someArray copyWithZone:nil];
 
NSDictionary *shallowCopyDict=[[NSDictionary alloc] initWithDictionary: someDictionary copyItems: NO];

These techniques are not restricted to the collections shown. For example, you can copy a set with the copyWithZone: method—or the mutableCopyWithZone: method—or an array with initWithArray:copyItems: method.

Deep Copies

There are two ways to make deep copies of a collection. You can use the collection’s equivalent of initWithArray:copyItems:with YES as the second parameter. If you create a deep copy of a collection in this way, each object in the collection is sent acopyWithZone: message. If the objects in the collection have adopted the NSCopying protocol, the objects are deeply copied to the new collection, which is then the sole owner of the copied objects. If the objects do not adopt the NSCopying protocol, attempting to copy them in such a way results in a runtime error. However, copyWithZone: produces a shallow copy. This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy, you can explicitly call for one as in Listing 2.

Listing 2  Making a deep copy

NSArray *deepCopyArray=[[NSArray alloc] initWithArray: someArray copyItems: YES];

This technique applies to the other collections as well. Use the collection’s equivalent of initWithArray:copyItems: with YES as the second parameter.

If you need a true deep copy, such as when you have an array of arrays, you can archive and then unarchive the collection, provided the contents all conform to the NSCoding protocol. An example of this technique is shown in Listing 3.

Listing 3  A true deep copy

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
          [NSKeyedArchiver archivedDataWithRootObject: oldArray]];

Copying and Mutability

When you copy a collection, the mutability of that collection or the objects it contains can be affected. Each method of copying has slightly different effects on the mutability of the objects in a collection of arbitrary depth:

  • copyWithZone: makes the sufrace level immutable. All deeper levels have the mutability they previously had.

  • initWithArray:copyItems: with NO as the second parameter gives the surface level the mutability of the class it is allocated as. All deeper levels have the mutability they previously had.

  • initWithArray:copyItems: with YES as the second parameter gives the surface level the mutability of the class it is allocated as. The next level is immutable, and all deeper levels have the mutability they previously had.

  • Archiving and unarchiving the collection leaves the mutability of all levels as it was before.

Objective-c语言的深、浅拷贝的区别就在于你对-(id)copyWithZone:(NSZone *)zone实现的区别.

有两种基本方式可以通过实现NSCopying 协议copyWithZone:方法来创建副本。您可以使用allocinit...,也可以使用NSCopyObject。要选择一种更适合于您的类的方式,您需要考虑以下问题:

  • 我需要深拷贝还是浅拷贝?

  • 我从超类继承NSCopying的行为了吗?

        一般来说,复制一个对象包括创建一个新的实例,并以原始对象中的值初始化这个新的实例。复制非指针型实例变量的值很简单,比如布尔,整数和浮点数。复制指针型实例变量有两种方法。一种方法称为浅拷贝,即将原始对象的指针值复制到副本中。因此,原始对象和副本共享引用数据。另一种方法称为深拷贝,即复制指针所引用的数据,并将其赋给副本的实例变量。

 

从超类继承 NSCopying

 

如果超类没有实现NSCopying,则您的类的实现必须复制它所继承的实例变量,以及那些在您的类中声明的实例变量。一般来说,完成这一任务的最安全的方式是使用allocinit...set方法。

使用“alloc, init...”方式

如果一个类没有继承NSCopying行为,则您应该使用allocinit...和set方法来实现copyWithZone:。例如,对于“独立副本”中提到的Product类,它的copyWithZone:方法可能会采用以下方式实现:


- (id)copyWithZone:(NSZone *)zone

{

    Product *copy = [[[self class] allocWithZone: zone]

            initWithProductName:[self productName]

            price:[self price]];

    [copy setDelegate:[self delegate]];

 

    return copy;

}

由于与继承实例变量相关的实现细节被封装在超类中,因此,一般情况下最好通过allocinit... 的方式实现NSCopying。这样一来就可以使用set方法中实现的策略来确定实例变量所需的复制类型。

使用 NSCopyObject()

如果一个类继承了NSCopying行为,则您必须考虑到超类的实现有可能会使用NSCopyObject函数。NSCopyObject通过复制实例变量的值而不是它们指向的数据,来创建对象的浅拷贝。例如,NSCellcopyWithZone:实现可能按照以下方式定义:


- (id)copyWithZone:(NSZone *)zone

{

    NSCell *cellCopy = NSCopyObject(self, 0, zone);

    

 

    cellCopy->image = nil;

    [cellCopy setImage:[self image]];

 

    return cellCopy;

}

在上面的实现中,NSCopyObject创建了原始cell对象的一份浅拷贝。这种行为适合于复制非指针型的实例变量,以及指向浅拷贝的非保留数据的指针型实例变量。指向保留对象的指针型实例变量还需要额外的处理。

在上面的copyWithZone:例子中,image是一个指向保留对象的指针。保留image的策略反映在下面setImage:存取方法的实现中。


- (void)setImage:(NSImage *)anImage

{

    [image autorelease];

    image = [anImage retain];

}

请注意,setImage:在重新对image赋值之前自动释放了image。如果上述copyWithZone:的实现没有在调用setImage:之前,显式地将副本的image实例变量设置为空,则副本和原始对象所引用的image将被释放,而没有被保留。

即使image指向了正确的对象,在概念上它也是未初始化的。不同于使用allocinit...创建的实例变量,这些未初始化的变量的值并不是nil值。您应该在使用它们之前显式地为这些变量赋予初始值。在这种情况下,cellCopyimage实例变量应该被设置为空,然后使用setImage:方法对它进行设置。

NSCopyObject的作用会影响到子类的实现。例如,NSSliderCell的实现可能会按照下面的方式复制一个新的titleCell实例变量。


- (id)copyWithZone:(NSZone *)zone

{

    id cellCopy = [super copyWithZone:zone];

    

 

    cellCopy->titleCell = nil;

    [cellCopy setTitleCell:[self titleCell]];

 

    return cellCopy;

}

其中,假设supercopyWithZone:方法完成这样的操作:


id copy = [[[self class] allocWithZone: zone] init];

你可能感兴趣的:(CopyWithZone:关于深拷贝/浅拷贝)