iOS Copy 测试:
测试代码:
// 测试Copy
NSMutableDictionary *dic1 = [NSMutableDictionary new];
NSMutableDictionary *dic2 = [dic1 copy];
NSLog(@"dic1:%p ---- dic2:%p:", dic1, dic2);
[dic2 setObject:@"test"forKey:@"test"];//会报错No Selector,因为该dic2指向的是NSDictionary对象,没有setObject这个方法
NSDictionary *dic3 = [NSDictionary dictionaryWithObjectsAndKeys:@"dfd",@"dsfds",nil];
NSDictionary *dic4 = [dic3 copy];
NSMutableDictionary *dic5 = [dic3 mutableCopy];
NSLog(@"dic3:%p ---- dic4:%p: ---- dic5:%p", dic3, dic4, dic5);
输出结果:
dic1:0x618000244260 ---- dic2:0x60800000dbf0:
dic3:0x60000002ff60 ---- dic4:0x60000002ff60: ---- dic5:0x60800005d490
Copy结论:
(1)Copy 拷贝的是不可编辑的对象,比如NSMutableDictionary 对象 的Copy会返回一个NSDictionary对象。
(2)Mutable对象 Copy到其他对象的话,是深拷贝,会新建一块内存,并把这个内存起始地址赋值给新对象;如果是不可变对象的Copy,那就是浅拷贝。因为Copy本身是拷贝一块不可变的对象,那么如果从不可变的对象(NSDictionary)拷贝,那么系统会默认给其他对象只是显示内存数值操作,所以只需要浅拷贝一个内存地址就可以实现,减少开辟内存花销(不论是不是同一个NSZone);而如果是从mutable可变对象拷贝copy,因为存在数据同步的问题(比如mutable修改了数据),那么就需要新建一块内存复制内容。
(3)mutableCopy一定是深拷贝。
另外copy 和metableCopy 对容器(Array或者Dictionary)不论是深拷贝或者浅拷贝,对于内部元素是指针拷贝(浅拷贝,retainCount+=1)。Apple文档将这个称为集合的单层深拷贝
浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制。
深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制。(比如Array只深复制Array的内存地址,里面数组元素浅拷贝)
完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。
集合的完全复制有两种方法:
(1)调用对象本身写好的API(如initWithArray: copyItems: 和 initWithDictionary: copyItems:,最后的参数copyItems设置为YES)
如果用这种方法深复制,集合里的每个对象都会收到 copyWithZone: 消息。如果集合里的对象遵循 NSCopying 协议,那么对象就会被深复制到新的集合(遵循上面的copy,不是mutableCopy,所以copy出来的是immutable,因此对于集合里的mutable对象,还是需要浅拷贝或者特殊处理mutableCopy)。如果对象没有遵循 NSCopying 协议,而尝试用这种方法进行深复制,会在运行时出错。
(2) 将集合进行归档(archive),然后解档(unarchive)
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
可以实现完全深复制,mutable对象会进行mutableCopy,而immutable对象会新建一块内存复制内容
// ---------------------------------------------------------------------------------------------------------------------------------------
// 测试新建内存
NSDictionary *dic6 = [NSDictionary new];
NSDictionary *dic7 = [NSDictionary new];
NSDictionary *dic8 = [NSDictionary dictionaryWithObject:@"test"forKey:@"test"];
NSLog(@"dic6:%p ---- dic7:%p: ---- dic8:%p", dic6, dic7, dic8);
NSMutableDictionary *dic9 = [NSMutableDictionary new];
NSMutableDictionary *dic10 = [NSMutableDictionary new];
NSMutableDictionary *dic11 = [NSMutableDictionary dictionaryWithObject:@"test"forKey:@"test"];
NSLog(@"dic9:%p ---- dic10:%p: ---- dic11:%p", dic9, dic10, dic11);
NSString *str5 = @"ddd";
NSString *str6 = @"ddd";
NSLog(@"str5:%p ---- str6:%p", str5, str6);
输出结果
dic6:0x61000000a030 ---- dic7:0x61000000a030: ---- dic8:0x608000035200
dic9:0x60000004a410 ---- dic10:0x610000049840: ---- dic11:0x6100000497b0
str5:0x105c8aa10 ---- str6:0x105c8aa10
新建内存结论:
(1)当编译器编译时发现新建OC对象指向的将是同一块内容的时候,那么系统将只开辟一块内存,然后把这个内存起始地址赋值给两个对象,所以dic9和dic10指向的地址是一样的。(str5和str6)
延伸:
实现深拷贝需要实现NSCopying、NSMutableCopying协议,当实现copy的时候,就是调用- (id)copyWithZone:(NSZone *)zone 方法。
copyWithZone返回不可变对象,而mutableCopyWithZone返回可变对象。
NSCopying和NSMutableCopying协议
@protocol NSCopying
- (id)copyWithZone:(nullableNSZone *)zone;
@end
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullableNSZone *)zone;
@end
如果是自定义的继承类(如继承NSObject)MyObject,那么子类MyObject对象使用Copy时会报错(No Selector),这是因为子类里面没实现copyWithZone这个方法;子类里面重写copyWithZone:
类似 @interface NSString :NSObject <NSCopying,NSMutableCopying,NSSecureCoding>
@interface MyObject : NSObject<NSCopying, NSMutableCopying>
- (id)copyWithZone:(NSZone*)zone
{
NSLog(@"zone:%p ------- defaultZone:%p", zone,NSDefaultMallocZone());
/** 如果MyObject的copyWithZone不需要被子类调用,可以直接用MyObject */
MyObject obj =[[MyObject allocWithZone:zone] init];
/** 如果MyObject的copyWithZone会在子类中被调用,那么需要用[self class],这样的话,在编译时[self class]会自动编译成当前调用的类名 */
MyObject obj = [[[self class] allocWithZone:zone] init];
obj.str = [self.str copy];
return obj;
}
输出结果:
zone:0x0 ------- defaultZone:0x10e569000
为什么copyWithZone的参数zone输出地址是为空,下一篇Zone重点说明吧