典型 cloning, copy, copyWithZone:, NSCopyObject()

来源:互联网 发布:君子知怕行文思路 编辑:程序博客网 时间:2024/06/11 10:25

转自: http://hi.baidu.com/yezehui2002/blog/item/8c328299f0c3631e6e068c8b.html

在 C++ 中,定义复制运算符和相关的操作是很重要的。在 Objective-C 中,运算法是不允许重定义的,所能做的就是要求提供一个正确的复制函数。

克隆操作在 Cocoa 中要求使用 NSCopying 协议实现。该协议要求一个实现函数:

-(id) copyWithZone:(NSZone*)zone;

这个函数的参数是一个内存区,用于指明需要复制那一块内存。Cocoa 允许使用不同的自定义区块。大多数时候默认的区块就已经足够,没必要每次都单独指定。幸运的是,NSObject 有一个函数

-(id) copy;

封装了 copyWithZone:,直接使用默认的区块作为参数。但它实际相当于 NSCopying 所要求的函数。另外,NSCopyObject() 提供一个不同的实现,更简单但同样也需要注意。下面的代码没有考虑 NSCopyObject():

// 如果父类没有实现 copyWithZone:,并且没有使用 NSCopyObject()-(id) copyWithZone:(NSZone*)zone {// 创建对象 Foo* clone =[[Foo allocWithZone:zone] init]// 实例数据必须手动复制 clone->integer = self->integer; // "integer" 是 int 类型的// 使用子对象类似的机制复制 clone->objectToClone =[self->objectToClone copyWithZone:zone]// 有些子对象不能复制,但是可以共享 clone->objectToShare =[self->objectToShare retain]// 如果有设置方法,也可以使用[clone setObject:self->object]return clone; }

注意,我们使用的是 allocWithZone: 而不是 alloc。alloc 实际上封装了 allocWithZone:,它传进的是默认的 zone。但是,我们应该注意父类的 copyWithZone: 的实现。

// 父类实现了 copyWithZone:,并且没有使用 NSCopyObject()-(id) copyWithZone:(NSZone*)zone { Foo* clone =[super copyWithZone:zone]// 创建新的对象// 必须复制当前子类的实例数据 clone->integer = self->integer; // "integer" 是 int 类型的// 使用子对象类似的机制复制 clone->objectToClone =[self->objectToClone copyWithZone:zone]// 有些子对象不能复制,但是可以共享 clone->objectToShare =[self->objectToShare retain]// 如果有设置方法,也可以使用[clone setObject:self->object]return clone; }

NSCopyObject()

NSObject 事实上并没有实现 NSCopying 协议(注意函数的原型不同),因此我们不能简单地使用 [super copy...] 这样的调用,而是类似 [[... alloc] init] 这种标准调用。NSCopyObject() 允许更简单的代码,但是需要注意指针变量(包括对象)。这个函数创建一个对象的二进制格式的拷贝,其原型是:

// extraBytes 通常是 0,可以用于索引实例数据的空间id NSCopyObject(id anObject, unsignedint extraBytes, NSZone*zone)

二进制复制可以复制非指针对象,但是对于指针对象,需要时刻记住它会创建一个指针所指向的数据的新的引用。通常的做法是在复制完之后重置指针。

// 如果父类没有实现 copyWithZone:-(id) copyWithZone:(NSZone*)zone { Foo* clone = NSCopyObject(self, 0, zone)// 以二进制形式复制数据// clone->integer = self->integer; // 不需要,因为二进制复制已经实现了// 需要复制的对象成员必须执行真正的复制 clone->objectToClone =[self->objectToClone copyWithZone:zone]// 共享子对象必须注册新的引用[clone->objectToShare retain]// 设置函数看上去应该调用 clone->object. 但实际上是不正确的,// 因为这是指针值的二进制复制。// 因此在使用 mutator 前必须重置指针 clone->object =nil[clone setObject:self->object]return clone; }   // 如果父类实现了 copyWithZone:-(id) copyWithZone:(NSZone*)zone { Foo* clone =[super copyWithZone:zone]// 父类实现 NSCopyObject() 了吗?// 这对于知道如何继续下面的代码很重要 clone->integer = self->integer; // 仅在 NSCopyObject() 没有使用时调用// 如果有疑问,一个需要复制的子对象必须真正的复制 clone->objectToClone =[self->objectToClone copyWithZone:zone]// 不管 NSCopyObject() 是否实现,新的引用必须添加 clone->objectToShare =[self->objectToShare retain]; clone->object =nil// 如果有疑问,最好重置[clone setObject:self->object]return clone; }

Dummy-cloning, mutability, mutableCopy and mutableCopyWithZone:

如果需要复制不可改变对象,一个基本的优化是假装它被复制了,实际上是返回一个原始对象的引用。从这点上可以区分可变对象与不可变对象。

不可变对象的实例数据不能被修改,只有初始化过程能够给一个合法值。在这种情况下,使用“伪克隆”返回一个原始对象的引用就可以了,因为它本身和它的复制品都不能够被修改。此时,copyWithZone: 的一个比较好的实现是:

-(id) copyWithZone:(NSZone*)zone {// 返回自身,增加一个引用return[self retain]}

retain 操作意味着将其引用加 1。我们需要这么做,因为当原始对象被删除时,我们还会持有一个复制品的引用。

“伪克隆”并不是无关紧要的优化。创建一个新的对象需要进行内存分配,相对来说这是一个比较耗时的操作,如果可能的话应该注意避免这种情况。这就是为什么需要区别可变对象和不可变对象。因为不可变对象可以在复制操作上做文章。我们可以首先创建一个不可变类,然后再继承这个类增加可变操作。Cocoa 中很多类都是这么实现的,比如 NSMutableString 是 NSString 的子类;NSMutableArray 是 NSArray 的子类;NSMutableData 是 NSData 的子类。

然而根据我们上面描述的内容,似乎无法从不可变对象安全地获取一个完全的克隆,因为不可变对象只能“伪克隆”自己。这个限制大大降低了不可变对象的可用性,因为它们从“真实的世界”隔离了出来。

除了 NSCopy 协议,还有一个另外的 NSMutableCopying 协议,其原型如下:

-(id) mutableCopyWithZone:(NSZone*)zone;

mutableCopyWithZone: 必须返回一个可变的克隆,其修改不能影响到原始对象。类似 NSObject 的 copy 函数,也有一个 mutableCopy 函数使用默认区块封装了这个操作。mutableCopyWithZone: 的实现类似前面的 copyWithZone: 的代码:

// 如果父类没有实现 mutableCopyWithZone:-(id) mutableCopyWithZone:(NSZone*)zone { Foo* clone =[[Foo allocWithZone:zone] init]// 或者可用 NSCopyObject() clone->integer = self->integer; // 类似 copyWithZone:,有些子对象需要复制,有些需要增加引用// 可变子对象使用 mutableCopyWithZone: 克隆//...return clone; }

不要忘记我们可以使用父类的 mutableCopyWithZone:

// 如果父类实现了 mutableCopyWithZone:-(id) mutableCopyWithZone:(NSZone*)zone { Foo* clone =[super mutableCopyWithZone:zone]//...return clone; }

原创粉丝点击