转自: http://hi.baidu.com/yezehui2002/blog/item/8c328299f0c3631e6e068c8b.html
在 C++ 中,定义复制运算符和相关的操作是很重要的。在 Objective-C 中,运算法是不允许重定义的,所能做的就是要求提供一个正确的复制函数。
克隆操作在 Cocoa 中要求使用 NSCopying 协议实现。该协议要求一个实现函数:
-
(
id
) copyWithZone
:
(
NSZone
*
)zone;
这个函数的参数是一个内存区,用于指明需要复制那一块内存。Cocoa 允许使用不同的自定义区块。大多数时候默认的区块就已经足够,没必要每次都单独指定。幸运的是,NSObject 有一个函数
封装了 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,
unsigned
int 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;
}