对象的复制
浅复制和深复制
用与一个实例对象相同的内容,生成一个新对象,这个过程一般称为复制。
只复制对象的指针称为浅复制(shallow copy),而复制具有新的内存空间的对象则称为深复制(deep copy)。
用指针共享某一对象的时候,同时也会共享那个对象的操作结果。而生成副本时,对源文件和副本的操作都是独立的。
区域
动态分配的内存管理称为区域。新运行时系统不使用区域。
因为区域在运行效率上并没有多大贡献,现在较新的运行时环境中都没有用到它。
通常生成实例对象要使用类方法alloc,而NSObject中也有一个类方法,可以指定区域来生成实例。
+ (id)allocWithZone:(NSZone)zone;
NSZone结构体是专门用于表达区域的数据结构。现代运行时中,zone一般设置为NULL。但方法的功能和alloc一样。
复制方法的定义
NSObject中有copy方法,它能够通过复制接收器来生成新实例。但是实际的复制操作并不是由copy来完成的,而是由实例方法copyWithZone:完成的。
鉴于这样的复制方式,光实现copy是不行的,还需要定义方法copyWithZone:,该方法返回复制生成的新对象。
由于方法copyWithZone:是在协议NSCopying中声明的,因此要在类中实现采用该协议的方法。如果该方法适用于协议NSCopying,那么在方法copy的声明属性指定为可选等情况下,编译器就会被告知可以进行复制。
@protocol
NSCopying
- (
id)copyWithZone:(NSZone*)zone;@end
方法copyWithZone:的定义方法可总结如下:
超类如果没有实现方法copyWithZone:,可使用alloc和init...来生成新实例,并将该实例变量谨慎的复制并带入。
超类如果实现了方法copyWithZone:,可以直接调用该方法来生成实例,再将子类中添加的实例变量根据需要复制并带入。
实例变量中共享对象没有必要复制。采用手动引用计数管理时需要retaion。
采用引用计数管理时,方法copyWithZone:及copy的调用者就是对象的所有者。因此该返回值不适用于autorelease。
对常数对象的类而言,定义方法copyWithZone:不一定要生成新对象。采用手动引用计数管理方式的情况下,通过self适用retain来返回结果。适用ARC和垃圾回收时,只返回self。
此外,还有另一个函数NSCopyObject(),它会将实例对象当作二进制序列完整地复制下来并生成另一个对象。但该函数容易出错,比较危险。所以不推荐使用。特别在使用ARC的情况下绝对要禁止使用该函数。
如果数据类型为id,则不可以使用->运算符。
实现可变复制
- (id)mutableCopy该方法是使用NSObject简单生成的,实际上真正生成实例的方法是mutableCopyWithZone:。
- (
id)mutableCopyWithZone:(NSZone *)NSZone如果自己需要定义一个包含了常数对象和可变对象的类,那么使用mutableCopyWithZone:方法会更加方便。
归档
对象的归档
定义:将对象打包成二进制文件就称为归档(archive)。
作用:将程序中使用的多个对象及其属性值,以及它们的相互关系保存到文件中,或者发送给另外的进程。
例如,将Xcode开发环境中构造的GUI对象的关系保存到名为nib的文件中,该文件就包括了各对象的属性值及对象之间的关系。这样就可以在开发环境中再次打来该文件进行编辑,或者在应用时使其作为实际的对象运行。
此外,在制作包含复制和粘贴,拖拽和释放等功能的应用程序时,为了保存操作对象的信息,有时也会利用归档。
并不是说归档适用于所有的数据保存,很多情况下,使用XML或属性表等高通用性的格式来保存数据,从程序的效率及稳定性上来说都会更好一些。
Foundation框架的归档功能
将对象存储转换为二进制序列的过程称为归档,打包或编码,逆变换则称为解档(unarchive),解码或对象还原。
一个对象指向其他对象,这种关系称为对象图。沿着对象的指向关系往下走,有时就会再次回到原来的对象,这样的情况称为闭环。Foundation框架可以将对象图归档为书库,其中作为出发点的对象就称为根对象。
可以使用NSKeyedArchiver和NSKeyedUnarchiver完成对象的归档和解档操作,而他们都是抽象类NSCoder的子类。
所有可以归档的对象都必须要适用于协议NSCoding。NSString,NSDictionary等Foundation框架的主要类都适用协议NSCoding。
协议NSCoding按照如下方式声明:
@protocol
NSCoding
- (
void)encodWithCoder:(NSCoder
*)aCoder;//归档
- (
void)initWithCoder:(NSCoder
*)aDecoder;//解档
@end
归档方法的定义
- (void
)encodeWithCoder:(NSCoder *)coder {
[
super
encodeWithCoder:coder];
//超类需要适用协议NSCoding
[coder encodeObject:
对象forKey:关键词字串
];
//或者使用encodeConditionalObject: forKey
...
[coder encodeDouble:
实数变量forKey:关键词字串
];
//有适合多种类型的方法
...
}
参数coder是NSCoder(具体为NSKeyedArchiver)的实例,该实例称为归档器。如果超类适用于协议NSCoding,那么super将调用encodeWithCoder对超类的实例变量进行归档。如果不适用,则不能调用。
接下来,类自身会对包含的实例变量进行归档。
解档方法的定义
参数coder是NSCoder(具体为NSKeyedArchiver)的实例,该实例称为解档器。
- (void
)initWithCoder:(NSCoder *)coder {
self= [super
initWithCoder:coder];
//超类不适用于协议NSCoding时,建议使用[ super init ];
变量= [coder decodeObjectForKey:键值
] retain];
...
变量= [coder decodeDoubleForKey:键值
];
...
returnself
;
}
只要在编码和解码中指定同样的键值,解码就可以按照任意顺序,即使有不能还原的变量也没有关系。
属性表
属性表概况
属性表(property list)是Cocoa环境中用来表示或保存各种信息的标准数据形式。它可以将数组或词典等对象的结构表现为字符串或二进制形式来保存在文件中,或者从中读取对象的信息。
归档其主要目的是在程序内部保存和还原对象。因此并不适合用来保存与具体的类关联较弱的信息。在保存及共享和程序的内部实现关系较弱的抽象数据时,建议使用属性表。
属性表有ASCII码,XML,二进制三种格式。ASCII是表示成文本,而二进制是表示成二进制文件。
在NSDictionary的接口中保存整个属性表时,该字典对象就称为根词典。字典的键值必须为字符串。
ASCII码格式属性表
只能从文件中读入,不能写出。它的存在主要是为了兼容性。实际程序中一般不使用。
但是作为一种字符串格式,这种属性表可以很容易地取出,看起来也一目了然,同时也可以用文本编辑器来编辑,因此在Debug或生成小的测试程序时也很有效。