实现对象复制
本文介绍了实现NSCopying
协议中的copyWithZone:方法的两种方式,都可以达到复制对象的目的。
有两种基本方式可以通过实现NSCopying协议的copyWithZone:
方法来创建副本。您可以使用alloc
和init...
,也可以使用NSCopyObject
。要选择一种更适合于您的类的方式,您需要考虑以下问题:
§ 我需要深拷贝还是浅拷贝?
§ 我从超类继承NSCopying
的行为了吗?
这些内容将在以下章节进行介绍。
一般来说,复制一个对象包括创建一个新的实例,并以原始对象中的值初始化这个新的实例。复制非指针型实例变量的值很简单,比如布尔,整数和浮点数。复制指针型实例变量有两种方法。一种方法称为浅拷贝,即将原始对象的指针值复制到副本中。因此,原始对象和副本共享引用数据。另一种方法称为深拷贝,即复制指针所引用的数据,并将其赋给副本的实例变量。
实例变量的set方法的实现应该能够反映出您需要使用的复制类型。如果相应的set方法复制了新的值,如下面的方法所示,那么您应该深拷贝这个实例变量:
- (void)setMyVariable:(id)newValue
|
{
|
[myVariable autorelease];
|
myVariable = [newValue copy];
|
}
|
如果相应的set方法保留了新的值,如下面的方法所示,那么您应该浅拷贝这个实例变量:
- (void)setMyVariable:(id)newValue
|
{
|
[myVariable autorelease];
|
myVariable = [newValue retain];
|
}
|
类似地,如果实例变量的set方法只是简单地将新的值赋给实例变量,而没有复制或保留它,那么您应该浅拷贝这个实例变量,正如下面的例子—虽然这通常很罕见:
- (void)setMyVariable:(id)newValue
|
{
|
myVariable = newValue;
|
}
|
独立副本
为了产生真正独立于原始对象的对象副本,必须对整个对象进行深拷贝。每个实例变量都必须被复制。如果实例变量本身具有实例变量,则它们也必须被复制,依此类推。在许多情况下,混合使用两种复制方式会更加有用。一般情况下,可以被视为数据容器的指针型实例变量往往被深拷贝,而更复杂的实例变量(如委托)则被浅拷贝。
@interface Product : NSObject <NSCopying>
|
{
|
NSString *productName;
|
float price;
|
id delegate;
|
}
|
|
@end
|
例如,Product类继承了NSCopying
。正如这个接口中所声明的,Product实例含有名称,价格和委托三个变量。
复制Product实例会产生productName
的一份深拷贝,这是因为它表示一个扁平的数据值。但是,delegate
实例变量是一个更复杂的对象,对于原始Product和副本Product都能够正常运行。因此,副本和原始对象应该共享这个委托。图1表示了Product实例及其副本在内存中的映像。
图 1浅拷贝与深拷贝实例变量
productName
的不同指针值说明,原始对象和副本各自都有自己的productName
字符串对象。而delegate
的指针值是相同的,这表明这两个product对象共享同一个对象作为它们的委托。
从超类继承 NSCopying
如果超类没有实现NSCopying
,则您的类的实现必须复制它所继承的实例变量,以及那些在您的类中声明的实例变量。一般来说,完成这一任务的最安全的方式是使用alloc
,init...
和set
方法。
另一方面,如果您的类继承了NSCopying
的行为,并声明了额外的实例变量,那么您也需要实现copyWithZone:
。在这个方法中,调用超类的实现来复制继承的实例变量,然后复制其他新声明的实例变量。您要如何处理新的实例变量,取决于您对超类的实现的熟悉程度。如果超类使用了或者有可能使用过NSCopyObject
,那么您必须有别于使用alloc
和init...
函数的情况,用不同的方式处理实例变量。
如果一个类没有继承NSCopying
行为,则您应该使用alloc
,init...
和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;
|
}
|
由于与继承实例变量相关的实现细节被封装在超类中,因此,一般情况下最好通过alloc
,init...
的方式实现NSCopying
。这样一来就可以使用set方法中实现的策略来确定实例变量所需的复制类型。
如果一个类继承了NSCopying
行为,则您必须考虑到超类的实现有可能会使用NSCopyObject
函数。NSCopyObject
通过复制实例变量的值而不是它们指向的数据,来创建对象的浅拷贝。例如,NSCell
的copyWithZone:
实现可能按照以下方式定义:
- (id)copyWithZone:(NSZone *)zone
|
{
|
NSCell *cellCopy = NSCopyObject(self, 0, zone);
|
/* Assume that other initialization takes place here. */
|
|
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
指向了正确的对象,在概念上它也是未初始化的。不同于使用alloc
和init...
创建的实例变量,这些未初始化的变量的值并不是nil
值。您应该在使用它们之前显式地为这些变量赋予初始值。在这种情况下,cellCopy
的image
实例变量应该被设置为空,然后使用setImage:
方法对它进行设置。
NSCopyObject
的作用会影响到子类的实现。例如,NSSliderCell
的实现可能会按照下面的方式复制一个新的titleCell
实例变量。
- (id)copyWithZone:(NSZone *)zone
|
{
|
id cellCopy = [super copyWithZone:zone];
|
/* Assume that other initialization takes place here. */
|
|
cellCopy->titleCell = nil;
|
[cellCopy setTitleCell:[self titleCell]];
|
|
return cellCopy;
|
}
|
其中,假设super
的copyWithZone
:方法完成这样的操作:
id copy = [[[self class] allocWithZone: zone] init];
|
超类的copyWithZone:
方法被调用,以复制继承而来的实例变量。当您调用超类的copyWithZone:
方法时,如果超类的实现有可能使用NSCopyObject
,则假定新的对象的实例变量是未初始化的。您应该在使用这些实例变量之前显式地为它们赋值。在这个例子中,在setTitleCell:
被调用之前,titleCell
被显式地设置为nil
。
当使用NSCopyObject
时,对象的保留计数的实现是另一个应该考虑的问题。如果一个对象将它的保留计数存储在一个实例变量中,则copyWithZone:
的实现必须正确地初始化副本的保留计数。图2说明了这个过程。
图 2复制过程中引用计数的初始化
图2中的第一个对象表示内存中的一个Product实例。refCount
中的值表明该对象已经被保留了3次。第二个对象是通过NSCopyObject
产生的Product实例的一份副本。它的refCount
值与原始对象一致。第三个对象表示从copyWithZone:
返回的一份副本,它的refCount
已被正确地初始化。copyWithZone:
在使用NSCopyObject
创建了副本之后,它将refCount
实例变量的值赋为1。copyWithZone:
的调用者隐式地保留了该副本,并负责释放它。
当“不可变的和可变的”这一概念应用于某个对象时,不论原始对象是不可变的还是可变的,NSCopying
总是产生不可变的副本。不可变的类可以非常有效地实现NSCopying
。由于不可变的对象不会发生改变,因此没有必要复制它们。相反,NSCopying
可以实现为retain
原始对象。例如,对于一个不可变的字符串类,copyWithZone:
可以按照下列方式实现。
- (id)copyWithZone:(NSZone *)zone {
|
return [self retain];
|
}
|
要使用NSMutableCopying
协议创建对象的可变副本。为了支持可变的复制,对象本身并不需要是可变的。该协议声明了mutableCopyWithZone:
方法。通常,可变的复制是通过NSObject
的便捷方法mutableCopy
调用的,该方法调用了默认zone的mutableCopyWithZone:
。
分享到:
相关推荐
这儿的运行时系统扮演的角色类似于Objective-C语言的操作系统,Objective-C基于该系统来工作。本文档将具体介绍NSObject类以及Objective-C程序是如何和运行时系统交互的。特别地,本文档还给出来怎样在运行时动态地加
objective-c运行时编程指南,
Objective-C高级编程 iOS与OS X多线程和内存管理.pdf
Objective-C高级编程 iOS与OS X多线程和内存管理.
《Objective-C 程序设计(第4版)》已经为iOS 5和Xcode4.2中的重大变更做了全面更新,最大的改动是引入了自动引用计数(ARC),并详细说明了如何在Objective-C编程过程中使用ARC提升和简化内存管理。
objective-c 面向对象编程 ,主要讲objiectve-c 的面向对象方面。
[Addison-Wesley Professional] Objective-C 编程 第6版 (英文版) [Addison-Wesley Professional] Programming in Objective-C 6th Edition (E-Book) ☆ 图书概要:☆ The book makes no assumptions about prior...
Objective-C 2.0运行时系统编程指南,有用指数5颗星。
objective-c 内存管理 alloc init release
帮助学习关于OC中内存管理的知识点
第7章到第10章讲述objective-c的基础框架,以及文件操作、内存管理、数据保存等内容。第11章讲述了应用工具框架。第12、13章分别讲述了如何开发iphone/ipad应用程序。第14章讲述了objective-c++和访问mysql数据库的...
Objective-C语言的许多决策可以在编译和运行时执行。只要有可能,它是动态的。这意味着Objective-C语言不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。Runtime系统是一种用于Objective-C语言的操作系统...
Objective-C内存管理 刚接触的的人可能有些迷惑,看了本文 你将成为Objective-C内存管理高手 文字 高清版
Objective-C是一种面向对象的编程语言,广泛应用于Mac OS和iOS开发。通过学习Objective-C的基本语法、类和对象、控制流程和方法等内容,你将能够编写简单的Objective-C程序并逐渐掌握更复杂的概念。继续学习和实践,...
Objective-C 内存管理 深入浅出发,熟悉内存管理。
使用objective-c语言实现的socket,有iphone平台下的,也有mac平台下的。对加深ios socket编程有一定帮助。
用Objective-C语言实现了各种设计模式,收集各种例子,方便大家学习和普及设计模式。.zip用Objective-C语言实现了各种设计模式,收集各种例子,方便大家学习和普及设计模式。.zip用Objective-C语言实现了各种设计...
本书结合理论知识与示例程序,全面而系统地讲述Objective-C编程的相关内容,包括Objective-C在C的基础上引入的特性和Cocoa工具包的功能及其中的框架,以及继承、复合、源文件组织等众多重要的面向对象编程技术。...
本书全面而系统地讲述Objective-C语言的基础知识和面向对象编程的重要概念,结合实例介绍了Cocoa工具包的优秀特性和其中的框架,以及继承、复合、源文件组织、内存管理、对象初始化和类别创建等众多重要的面向对象...