"OC基础"这个分类的文章是我在自学Stephen G.Kochan的《Objective-C程序设计第6版》过程中的笔记。
1、浅复制即是指针赋值,复制对象的修改会影响被复制对象;深复制即是真正的对象复制,会复制出来一个副本,这个副本和原对象是完全分开的。
Foundation类实现了两个复制的方法:copy和mutableCopy。copy复制出来的是一个不可变的副本,mutableCopy复制出来的是一个可变的副本。
同时注意,产生一个对象的可变副本并不要求被复制的对象本身是可变的,同样的,产生一个不可变副本也不要求被复制对象本身是不可变的。
2、在iOS中,并不是所有的对象都支持copy和mutableCopy,只有遵守NSCopying协议的类可以发送copy消息,遵守NSMutableCopying协议的类才可以发送mutableCopy消息。但是默认的自定义类并没有遵守这两个协议。所以如果想自定义使用copy,那么就必须遵守NSCopying协议,并且实现copyWithZone:方法,如果想自定义使用mutableCopy,那么就必须遵守NSMutableCopying协议,并且实现mutableCopyWithZone:方法。
3、深复制和浅复制的操作:
iOS提供了copy和mutablecopy两个方法,顾名思义,copy方法复制出来的是一个不可变对象,而mutablecopy就是复制出来的就是一个mutable的对象。以下将分两种情况来说明:
(1)、系统的非容器类对象(即是指NSString、NSNumber等这一类的对象)情况1:
...
NSString *s = @"abcd";
NSString *sCopy = [s copy];
NSMutableString *mutaSMutaCopy = [s mutableCopy];
[mutaSMutaCopy appendString:@"ef"];
...
在这种情况下,s和sCopy指向的是同一块内存区域,此时sCopy的引用计数和s一样都为2。而sMCopy则是我们所认为的真正意义上的复制,系统为其分配了新内存,但指针所指向的字符串还是和s所指的一样。
(2)、系统的非容器类对象情况2:
...
NSMutableString *s = [NSMutableString stringWithString: @"abcd"];
NSString *sCopy = [s copy];
NSMutableString *mutaSCopy = [s copy]; //Mutable S Copy,它其实是不可变的
NSMutableString *sMutaCopy = [s mutableCopy];
[mutaSCopy appendString:@"ef"]; //这句会产生错误,因为mSCopy其实不可变的
[s appendString:@"ef"];
[sMutaCopy appendString:@"gh"];
...
以上四个NSString对象所分配的内存都是不一样的。由于mutaSCopy其实是个不可变对象,所以会有上述的报错。所以对于系统的非容器类对象,我们可以认为:
对不可变对象做复制,copy是指针复制(浅复制),而mutableCopy就是对象复制(深复制);如果是对可变对象进行复制,就全部都是深复制,这种情况需要注意:使用copy复制返回的对象无论如何都会是不可变的。
(3)、系统的容器类对象 (即是指NSArray、NSDictionary类等)情况1:
对于容器类,上面的结论也是适用的,需要探讨的是复制后容器内对象的变化,看以下代码:
...
NSArray *a = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
NSArray *aCopy = [a copy];
...
aCopy和a是同一个NSArray对象(指向相同的对象),包括array里面的元素也是指向相同的指针,所以只是指针复制。再看下一个语句:
...
NSMutableArray *aMCopy = [a mutableCopy];
...
aMCopy是a的可变副本,指向的对象就和a不同了,但是其中的元素和a中的元素指向的仍然是同一个对象。aMCopy还可以修改自己的对象:
...
[aMCopy addObject:@"de"];
[aMCopy removeObjectAtIndex: 0];
...
从上面的例子可以看出:aCopy是指针复制,而aMCopy是对象复制,aMCopy还可以删除或添加其元素。但是需要注意的是,容器内的元素的内容都是指针复制。
(4)、系统的容器类对象情况2:
下面再看另一种情况:
...
NSArray *a = [NSArray arrayWithObjects: [NSMutableString stringWithString: @"a"], @"b", @"c", nil];
NSArray *aCopy = [a copy];
NSMutableArray *aMCopy = [a mutableCopy];
...
aCopy,aMCopy和a指向的都是不一样的对象,但是其中的元素都是一样的,因为元素内容存储的是指针,还是指向同一个位置。看以下代码:
...
NSMutableString *testString = [a objectAtIndex:0];
[testString appendString:@" tail"];
...
然后可以发现三个数组的首元素都被改变。注意,在这里进行赋值的时候,使用testString = @"tail"这样赋值是不行的,因为这样其实是改变了testString的指针指向,将@“tail”临时对象赋给了testString。
(5)、由此可见,对于容器而言,其元素对象始终是指针复制。如果需要元素对象也是对象复制,就需要实现深拷贝:
NSArray *array = [NSArray arrayWithObjects: [NSMutableString stringWithString: @"first"] ,[NSString stringWithString:@"b"], @"c", nil];
NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES];
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject: array]];
trueDeepCopyArray是完全意义上的深拷贝,而deepCopyArray则还不是,对于deepCopyArray内的不可变元素,deepCopyArray使用的还是指针复制。
因为如果容器的某一元素是不可变的,那你复制完后该对象仍旧是不能改变的,因此只需要指针复制即可。这种情况下,哪怕你对容器内的元素重新赋值,也不会影响到其他的容器。举个例子,使用[array objectAtIndex: 0] = @”sd”之后,其他的容器内对象并不会受影响。
[array objectAtIndex: 1]和[deepCopyArray objectAtIndex: 0]尽管是指向同一块内存,但是我们没有办法对其进行修改——因为它是不可改变的。所以指针复制已经足够。所以虽然这并不是完全意义上的深拷贝,但是apple的官方文档将其列为deep copy了。
4、使用Foundation类的copy或mutableCopy方法是,深复制并不是默认执行的。从3中也可以看出,对不可变对象使用copy的效果也仅仅是指针复制。
5、关于实现自定义类的copy方法:
(1)、首先自定义类必须是Foundation类的子类;
(2)、要为自定义类实现copy方法,必须先实现<NSCopying>协议,再重载copyWithZone方法;要为自定义类实现mutableCopy方法,必须先实现<NSMutableCopying>协议,然后重载mutableCopyWithZone方法;因为自定义类的copy和mutableCopy是通过调用copyWithZone和mutableCopyWithZone来实现的;
(3)、以自定义分数类Fraction类为例,它含有两个实例变量:分母d和分子n,要实现这个类的copy方法,首先应该在@interface部分引入<NSCopying>协议:
@interface Fraction: NSObject <NSCopying>
然后在@implementation部分添加以下内容来重载copyWithZone方法:
- (id) copyWithZone: (NSZone *) zone {
id newFraction = [[[self class] allocWithZone] init];
//这么写是考虑到可能有子类继承会这个方法
[newFraction setTo: n over: d]; //使用原有的设值方法
return newFraction;
}
(4)、如果父类本身已实现了copy或者mutableCopy方法,那么在重载的过程中应先调用父类的copy方法,再添加附加的内容;
6、在合成设置属性的时候,如果使用以下语句合成属性:
@property (nonatomic, copy) NSString *name;
...
@synthesize name;
...
那么编译器自动生成的setter方法是:
-(void) setName: (NSString *) theName {
if(theName != name)
name = [theName copy];
}
其实也就是使用了copy方法了。
7、属性没有mutableCopy特性。即使是可变的实例变量也是使用copy方法去复制,所以都会生成一个对象的不可变副本。
8、在copy方法中,如果正在复制的实例变量包含不可变的对象(比如不可变的字符串对象),那么系统其实并没有生成这个对象内容的副本。仅仅生成一个对于这个对象的新引用,就足够了,因为反正它也是不可变的,不可能会有改动副本影响到原对象这种情况发生。