在iOS中并不是所有的对象都支持copy,mutableCopy,遵守NSCopying/NSMutableCopying 协议的类可以发送copy/mutableCopy消息,否则就会发生异常。默认的iOS类并没有遵守这两个协议。如果想自定义一下copy/mutableCopy 那么就必须遵守NSCopying/NSMutableCopying,并且实现 copyWithZone: /mutableCopyWithZone:方法.
copy 和 mutableCopy分两种情况讨论,非容器类对象和容器类对象。需要有一个注意的点是,copy返回的对象是不可变的。
一 、对于非容器类对象:
这里指的是NSString,NSNumber等等一类的对象
- retain:对象引用计数器自增+1,与原对象指向同一地址。结果和浅拷贝一样,
- copy:
- 对不可变对象 执行浅拷贝,等同与retain,指针拷贝,引用计数器+1(同上);
- 对可变对象 执行深拷贝,等同mutableCopy,对象拷贝(同下)。
- mutableCopy:
- 对不可变对象 执行深拷贝,创建新的实例对象,分配了一块新的内存,拷贝原对象的值。
- 对可变对象 执行深拷贝,创建新的实例对象,分配了一块新的内存,拷贝原对象的值。
实际验证一下,结果显而易见:
//对非容器不可变对象copy
NSString * str = @"a";
NSString * strCopy = [str copy];
NSLog(@"对非容器不可变对象copy");
NSLog(@"str %p",str);
NSLog(@"strCopy %p",strCopy);
//对非容器可变对象copy
NSMutableString * mstr = [[NSMutableString alloc] init];
mstr.string = @"b";
NSMutableString * mstrCopy = [mstr copy];
NSLog(@"对非容器可变对象copy");
NSLog(@"mstr %p",mstr);
NSLog(@"mstrCopy %p",mstrCopy);
//对非容器不可变对象mutablecopy
NSString * strMutableCoy = [str mutableCopy];
NSLog(@"对非容器不可变对象mutablecopy");
NSLog(@"str %p",str);
NSLog(@"strMutableCoy %p",strMutableCoy);
//对非容器可变对象mutablecopy
NSMutableString * mstrMutableCopy = [mstr mutableCopy];
NSLog(@"对非容器可变对象mutableCopy");
NSLog(@"mstr %p",mstr);
NSLog(@"mstrMutableCopy %p",mstrMutableCopy);
综上,对于非容器类对象,我们可以认为 :
- 不可变对象复制,copy是浅拷贝(指针复制)和mutableCopy就是深拷贝(对象复制)。
- 对可变对象复制,都是深拷贝。
二 、对于容器类对象:
这里指的是NSArray,NSDictionary等。对于容器类对象本身,上面的结论同样适用。
需要探讨的是复制后 容器内元素对象 的变化。
//对容器 不可变对象 copy
NSArray * arr = [NSArray arrayWithObjects:@"a",@"b", nil];
NSArray * arrCopy = [arr copy];
NSLog(@"对容器不可变对象copy");
NSLog(@"arrarr.lastObject %p",arr.lastObject);
NSLog(@"arrCopy.lastObject %p",arrCopy.lastObject);
//对容器 可变对象 copy
NSMutableArray * mArr = [[NSMutableArray alloc] initWithObjects:@"a",@"b", nil];
NSMutableArray * mArrCopy = [mArr copy];
NSLog(@"对容器可变对象copy");
NSLog(@"mArr.lastObject %p",mArr.lastObject);
NSLog(@"mArrCopy.lastObject%p",mArrCopy.lastObject);
//对容器 不可变对象 mutablecopy
NSArray * arrMutableCopy = [arr mutableCopy];
NSLog(@"对非容器不可变对象mutablecopy");
NSLog(@"arr.lastObject %p",arr.lastObject);
NSLog(@"arrMutableCopy.lastObject %p",arrMutableCopy.lastObject);
//对容器 可变对象 mutablecopy
NSMutableArray * mArrMutableCopy = [mArr mutableCopy];
NSLog(@"对非容器可变对象mutableCopy");
NSLog(@"mArr.lastObject %p",mArr.lastObject);
NSLog(@"mArrMutableCopy.lastObject %p",mArrMutableCopy.lastObject);
综上,对于容器类对象,我们可以认为 :
- 对于容器类对象本身,copy/mutablecopy 执行结果同非容器类对象。
- 对于容器类对象 容器内的元素对象,都是浅拷贝。
如何实现容器内元素对象的深拷贝
两种方法
NSArray * arr = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"x"],@"a",@"b",[NSMutableString stringWithString:@"c"], nil];
NSArray * arrDeepCopy=[[NSArray alloc] initWithArray: arr copyItems: YES];
NSArray * arrTrueDeepCopy = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: arr]];
NSLog(@"%p %p %p",arr.firstObject, [arr objectAtIndex:1], arr.lastObject);
NSLog(@"%p %p %p",arrDeepCopy.firstObject, [arrDeepCopy objectAtIndex:1], arrDeepCopy.lastObject);
NSLog(@"%p %p %p",arrTrueDeepCopy.firstObject,[arrTrueDeepCopy objectAtIndex:1], arrTrueDeepCopy.lastObject);
综上可见:
- arrTrueDeepCopy 使用 归档解档操作 是完全意义上的深拷贝,拷贝了元素对象本身;
- arrDeepCopy 使用 copyItems 是官方文档定义的深拷贝方法。其实不是,arrDeepCopy内的不可变元素其实还是指针复制。原因如下。
因为如果容器的某一元素是不可变的,复制完后该对象仍旧是不能改变的,因此只需要指针复制即可。除非你对容器内的元素重新赋值,否则指针复制即已足够。
举个例子,[[arr objectAtIndex:0]appendstring:@”123”] 后其他的容器内对象并不会受影响(深拷贝的)。[[arr objectAtIndex:1] 和 [[arrDeepCopy objectAtIndex:1] 尽管是指向同一块内存地址,但是我们没有办法对其进行修改——因为它是不可改变的。所以指针复制已经足够。所以这并不是完全意义上的深拷贝,但是apple的官方文档将其列为deep copy了,并添加了copy和mutablity的关系说明,故在此做一说明。
receiver是不可变对象的时候,接收到copy消息时。只要达到retain的效果就可以了。这样一方面可以满足定义的要求,另一主面可以节省资源。这也是为什么copy和mutablecopy会有这样的实现。
具体应用
在修饰不可变的对象时,例如NSString 和NSArray 等等时,推荐使用copy 而非其他修饰,更有安全性。
相关参考链接 c杂谈之指针与数组