首页 Objective-C 从 C++ 到 Objective-C(12):实例化(续三)

从 C++ 到 Objective-C(12):实例化(续三)

0 1.6K

复制运算符

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

在 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:而不是allocalloc实际上封装了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,mutableCopyandmutableCopyWithZone:

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

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

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

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

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

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

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

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

mutableCopyWithZone:必须返回一个可变的克隆,其修改不能影响到原始对象。类似NSObjectcopy函数,也有一个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;
}

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2