内存管理(五)深浅复制

深浅复制

深复制与浅复制

  • 深复制

    • 源对象和副本对象不同的各两个对象
    • 源对象的引用计数器不变,副本对象的引用计数器为 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底层源码分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值