深浅复制
深复制与浅复制
-
深复制
- 源对象和副本对象不同的各两个对象
- 源对象的引用计数器不变,副本对象的引用计数器为 1(因为是新产生的)
- 本质产生了新的对象
-
浅复制
- 源对象和副本对象是一个对象
- 源对象(副本对象)引用计数器 + 1,相当于做了一次 retain 操作
- 本质没有产生新的对象
复制与计数器
- 深复制
NSString *str = [NSString stringWithFormat:@"55555555555555555555"];
NSMutableString *str2 = [str mutableCopy];
NSLog(@"str=%ld, str2=%ld", CFGetRetainCount((__bridge CFTypeRef)str), CFGetRetainCount((__bridge CFTypeRef)str2));
[str2 release];
输出
str=1, str2=1
(lldb) p str
(__NSCFString *) $1 = 0x0000600001a30870 @"55555555555555555555"
(lldb) p str2
(__NSCFString *) $2 = 0x0000600001a308d0 @"55555555555555555555"
(lldb)
分析
产生了新对象,新对象(副本对象)的计数器是1, 源对象的计数器不变
- 浅复制
NSString *str = [NSString stringWithFormat:@"55555555555555555555"];
NSString *str2 = [str copy];
NSLog(@"str=%ld, str2=%ld", CFGetRetainCount((__bridge CFTypeRef)str), CFGetRetainCount((__bridge CFTypeRef)str2));
[str2 release];
输出
str=2, str2=2
(lldb) p str
(__NSCFString *) $0 = 0x00006000015f67c0 @"55555555555555555555"
(lldb) p str2
(__NSCFString *) $1 = 0x00006000015f67c0 @"55555555555555555555"
(lldb)
分析
没有产生新对象, 源对象(副本对象)的计数器会再次+1
copy 与 mutableCopy
copy产生不可变副本,mutableCopy产生可变副本
理论上讲OC中的任何一个对象都可以复制(copy 和 mutableCopy ),只是Foundation库中的类的对象可以直接复制,自定义的类的对象需要做一些额外的工作才能复制,但实际做app几乎不需要复制自定义类的对象。
不可变对象和可变对象的 copy 、mutableCopy 对比
不可变对象copy成不可变对象是浅复制,其他都是深复制。(不可变对象指NSArray、NSDictionary、NSString等)
我们以NSString为例 进行4种对比分析
NSString对象不一定都是存储在堆区的对象,为了防止NSString对象出现在栈区 比如NSTaggedPointerString类型,干扰我们分析深浅copy 我故意把字符串写成55555555555555555555 写的长一点。
不可变对象copy 浅copy副本源本是一个
NSString *str = [NSString stringWithFormat:@"55555555555555555555"];
NSString *str2 = [str copy];
[str2 release];
输出
-[VC3 dealloc]
不可变对象 mutableCopy 深copy出新副本 可变副本
NSString *str = [NSString stringWithFormat:@"55555555555555555555"];
NSString *str2 = [str mutableCopy];
[str2 appendString:@"杰伦轨迹"];
NSLog(@"str2==%@",str2);
[str2 release];
输出
str2==55555555555555555555杰伦轨迹
-[VC3 dealloc]
分析
这种写法有点变态 虽然能运行成功 一般不这么写
可变对象 深copy出新副本 副本不可变 增删会崩溃
NSMutableString *str = [NSMutableString string];
NSMutableString *str2 = [NSMutableString stringWithString:@"888111111111111"];;
str2 = [str copy];
[str2 appendString:@"杰伦crash"];
[str2 release];
输出
(lldb) n
(lldb) n
(lldb) p str2
(__NSCFString *) $0 = 0x0000600001481b30 @"888111111111111"
(lldb) n
(lldb) p str2
(__NSCFConstantString *) $1 = 0x00007fff801c37d8 @""
(lldb)
// 崩溃
Terminating app due to uncaught exception 'NSInvalidArgumentException'
分析
__NSCFString 就是NSMutableString
__NSCFConstantString 就是NSString
copy 产生不可变新副本 copy之后 str2从可变字符串变成了不可变字符串,没有响应的增删改方法,所以对其进行增删改操作就会报错。
可变对象 mutableCopy 深copy出新副本 可变副本
NSMutableString *str = [NSMutableString string];
NSMutableString *str2 = [NSMutableString stringWithString:@"888111111111111"];;
str2 = [str mutableCopy];
[str2 appendString:@"杰伦借口"];
[str2 release];
输出
str2==杰伦借口
-[VC3 dealloc]
参考图
数组和和字典就不演示了,和上述字符串一样 这个图都总结了
copy 与 strong / retain
用copy修饰表示不希望值跟随外部改变,用strong修饰会跟随指向内存地址的内存的改变而改变
NSMutableArray 被 copy 、strong 修饰后的变化
把NSMutableArray用copy修饰有时就会crash,因为对这个数组进行了增删改操作,而copy后的数组变成了不可变数组NSArray,没有响应的增删改方法,所以对其进行增删改操作就会报错。
代码如下
NSMutableArray *b = [NSMutableArray array];
NSMutableArray *arr;
arr = [b copy];
[arr addObject:@"1"];
[b release];
输出
(lldb) p b
(__NSArrayM *) $0 = 0x0000600000aa01e0 @"0 elements"
(lldb) p arr
(NSMutableArray *) $1 = 0x3ff0000000000000
(lldb) n
(lldb) p arr
(__NSArray0 *) $2 = 0x00007fff8004b160 @"0 elements"
// 不识别addObject 方法
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArray0 addObject:]: unrecognized selector sent to instance 0x7fff8004b160'
分析
__NSArray0 不可变数组 __NSArrayM不可变数组
b被copy后就成了NSArray了 注意下数组类别的变化,如图:
__NSArrayI __NSArrayM 底层分析看我这篇文章
如果是strong 或者 retain 直接就是赋值 不存在copy右边是什么,左边就是什么,并且是强引用新值
使用 retain
NSMutableArray *b = [NSMutableArray array];
NSMutableArray *arr;
arr = [b retain];
[arr addObject:@"1"];
NSLog(@"arr==%@",arr);
NSLog(@"b==%@",b);
[b release];
输出
arr==(
1
)
b==(
1
)
(lldb) p b
(__NSArrayM *) $0 = 0x00006000018d55c0 @"1 element"
(lldb) p arr
(__NSArrayM *) $1 = 0x00006000018d55c0 @"1 element"
-[VC3 dealloc]
使用 strong
以上的例子变成属性也都一样,我们这次例子用属性
要先了解 copy属性 strong属性 到底都做了些啥 setter方法内部是怎样的 详细可以看这篇文章
@property (nonatomic, strong) NSMutableArray *a;
NSMutableArray *b = [NSMutableArray array];
self.a = b;
[self.a addObject:@"1"];
NSLog(@"self.a==%@",self.a );
NSLog(@"b==%@",b);
[_a release];
输出
self.a==(
1
)
b==(
1
)
(lldb) p self.a
(__NSArrayM *) $0 = 0x0000600003068c60 @"1 element"
(lldb) p b
(__NSArrayM *) $1 = 0x0000600003068c60 @"1 element"
-[VC3 dealloc]
总结
- NSString、NSArray、NSDictionary 等经常使用 copy 关键字,是因为他们有对应的可变类型 NSMutableString、NSMutableArray、NSMutableDictionary,他们之前可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性时拷贝一份
- 注意可变类型执行copy之后生成不可变新副本 容易引发崩溃。
- 用copy修饰表示不希望值跟随外部改变,用strong修饰会跟随指向内存地址的内存的改变而改变, 如果你不在意值跟随外部改变 你想用什么用什么 strong retain 都可以。
点我查看copy底层源码分析