内存

iOS-浅拷贝与深拷贝

2020-06-06  本文已影响0人  JimmyCJJ

当你的才华撑不起你的野心时,你就应该静下来学习。 —— CJJ

Why Learn?

浅拷贝和深拷贝是必须要掌握的知识点,工作中也会频繁用到,所以在这里记录总结一下自己的理解,方便日后查看。

Ask?

如果你觉得浅拷贝就是copy,深拷贝就是mutableCopy的话,那就大错特错了,请继续往下看

定义

首先浅拷贝是什么?深拷贝又是什么呢?

这两种都是拷贝(复制)对象的意思,不同的是

简单对比

浅拷贝:拷贝指针,不开辟内存地址
深拷贝:拷贝指针和内容,开辟内存地址(存放拷贝的内容)

如何判断?

要判断一个拷贝是浅拷贝还是深拷贝,不仅看copy还是mutableCopy,还要考虑拷贝的对象是可变的还是不可变的

举例说明,假如存在不可变对象A可变对象B,对AB分别进行copymutableCopy,结果如下

NSString *A = @"aaa";
id C = [A copy];
NSLog(@"\n不可变-copy\n%p = 原对象指针地址\n%p = copy的对象指针地址\n%p = 原对象的内存地址 - %@\n%p = copy的对象的内存地址 - %@",&A,&C,A,A,C,C);

控制台打印

2020-06-03 17:52:29.341114+0800 Demo[12665:880626] 
不可变-copy
0x7ffee06c70e0 = 原对象指针地址
0x7ffee06c70d8 = copy的对象指针地址
0x10f53d358 = 原对象的内存地址 - aaa
0x10f53d358 = copy的对象的内存地址 - aaa

从打印结果我们可以看出,对不可变对象进行copy

  • 指向内存地址的指针地址不一样
  • 内存地址一样(存放的内容都是aaa

所以这是一个浅拷贝

NSString *A = @"aaa";
id C = [A mutableCopy];
NSLog(@"\n不可变-mutableCopy\n%p = 原对象指针地址\n%p = copy的对象指针地址\n%p = 原对象的内存地址 - %@\n%p = copy的对象的内存地址 - %@",&A,&C,A,A,C,C);

控制台打印

2020-06-03 17:53:01.600945+0800 Demo[12679:881248] 
不可变-mutableCopy
0x7ffee97830e0 = 原对象指针地址
0x7ffee97830d8 = copy的对象指针地址
0x106481358 = 原对象的内存地址 - aaa
0x60000209ab20 = copy的对象的内存地址 - aaa

从打印结果我们可以看出,对不可变对象进行mutableCopy

  • 指向内存地址的指针地址不一样
  • 内存地址不一样,开辟了新的内存地址(存放的内容都是aaa

所以这是一个深拷贝

NSMutableString *B = [NSMutableString stringWithString:@"bbb"];
id C = [B copy];
NSLog(@"\n可变-copy\n%p = 原对象指针地址\n%p = copy的对象指针地址\n%p = 原对象的内存地址 - %@\n%p = copy的对象的内存地址 - %@",&B,&C,B,C,B,C);

控制台打印

2020-06-03 17:51:22.168009+0800 Demo[12647:879529] 
可变-copy
0x7ffedfc920e0 = 原对象指针地址
0x7ffedfc920d8 = copy的对象指针地址
0x600003af3cc0 = 原对象的内存地址 - bbb
0xd6bb373121e18d02 = copy的对象的内存地址 - bbb

从打印结果我们可以看出,对可变对象进行copy

  • 指向内存地址的指针地址不一样
  • 内存地址不一样,开辟了新的内存地址(存放的内容都是bbb

所以这是一个深拷贝

NSMutableString *B = [NSMutableString stringWithString:@"bbb"];
id C = [B mutableCopy];
NSLog(@"\n可变-mutableCopy\n%p = 原对象指针地址\n%p = copy的对象指针地址\n%p = 原对象的内存地址 - %@\n%p = copy的对象的内存地址 - %@",&B,&C,B,B,C,C);

控制台打印

2020-06-03 17:54:34.595064+0800 Demo[12717:882756] 
可变-mutableCopy
0x7ffee78cf0e0 = 原对象指针地址
0x7ffee78cf0d8 = copy的对象指针地址
0x600001143690 = 原对象的内存地址 - bbb
0x600001143660 = copy的对象的内存地址 - bbb

从打印结果我们可以看出,对不可变对象进行mutableCopy

  • 指向内存地址的指针地址不一样
  • 内存地址不一样,开辟了新的内存地址(存放的内容都是bbb

所以这是一个深拷贝

总结

[immutableObject copy];        //浅拷贝,拷贝后的对象不可变
[immutableObject mutableCopy]; //深拷贝,拷贝后的对象可变
[mutableObject copy];          //深拷贝,拷贝后的对象不可变(不安全,不建议用)
[mutableObject mutableCopy];   //深拷贝,拷贝后的对象可变

通过四种不同情况的代码测试,最终得到的结论是

浅拷贝与深拷贝

拓展 - 面试题:修饰属性时用strong还是copy?

最安全的做法(推荐):修饰不可变对象(NSStringNSArrayNSDictionary等)用copy,修饰可变对象(NSMutableStringNSMutableArrayNSMutableDictionary等)用strong

@property (nonatomic,strong) NSString *strStrong;
@property (nonatomic,copy) NSString *strCopy;

测试一:strong修饰,赋值不可变

NSString *immutableStr = @"aaa";
self.strStrong = immutableStr;
immutableStr = @"bbb";

经过LLDB调试可以看出,运行过了前两行代码,immutableStrstrStrong指向同一片内存地址,但是他们的指针变量是不一样的

//运行完前两句代码
//NSString *immutableStr = @"aaa";
//self.strStrong = immutableStr;
//指向的内存地址相同,都是0x000000010bb3d338
(lldb) p immutableStr
(__NSCFConstantString *) $0 = 0x000000010bb3d338 @"aaa"
(lldb) p _strStrong
(__NSCFConstantString *) $1 = 0x000000010bb3d338 @"aaa"
//指针地址不同,0x00007ffee40c70e8和0x00007fd7194098b8
(lldb) p &immutableStr
(NSString **) $2 = 0x00007ffee40c70e8
(lldb) p &_strStrong
(NSString **) $3 = 0x00007fd7194098b8

//运行完第三句代码
//immutableStr = @"bbb";
(lldb) p immutableStr
(__NSCFConstantString *) $4 = 0x000000010bb3d358 @"bbb"
(lldb) p &immutableStr
(NSString **) $5 = 0x00007ffee40c70e8
(lldb) p _strStrong
(__NSCFConstantString *) $6 = 0x000000010bb3d338 @"aaa"
(lldb) p &_strStrong
(NSString **) $7 = 0x00007fd7194098b8

第三句代码的意思是让immutableStr指针变量指向存放@"bbb"的新的内存地址,而且这个操作并不影响strStrong,因为只是改变了immutableStr的指针指向,所以再次打印strStrong的指针地址和内存地址还是跟原来一样,没有变化。
tips:immutableStr是不可变对象,不能改变所指向内存地址的值,只能重新生成一块新的内存地址并指向它。

测试二:strong修饰,赋值可变

NSMutableString *mutableStr = [NSMutableString stringWithString:@"aaa"];
self.strStrong = mutableStr;
[mutableStr appendString:@"bbb"];

经过LLDB调试可以看出,运行过了前两行代码,mutableStrstrStrong指向同一片内存地址,但是他们的指针变量是不一样的

//运行完前两句代码
//NSMutableString *mutableStr = [NSMutableString stringWithString:@"aaa"];
//self.strStrong = mutableStr;
(lldb) p mutableStr
(__NSCFString *) $0 = 0x00006000027ddc20 @"aaa"
(lldb) p _strStrong
(__NSCFString *) $1 = 0x00006000027ddc20 @"aaa"
(lldb) p &mutableStr
(NSMutableString **) $2 = 0x00007ffeee7460e8
(lldb) p &_strStrong
(NSString **) $3 = 0x00007f871e40b658

//运行完第三句代码
//[mutableStr appendString:@"bbb"];
(lldb) p mutableStr
(__NSCFString *) $4 = 0x00006000027ddc20 @"aaabbb"
(lldb) p _strStrong
(__NSCFString *) $5 = 0x00006000027ddc20 @"aaabbb"
(lldb) p &mutableStr
(NSMutableString **) $6 = 0x00007ffeee7460e8
(lldb) p &_strStrong
(NSString **) $7 = 0x00007f871e40b658

解释:由于mutableStr是可变对象,所以它可以在mutableStr指向的那片内存的字符串后面追加字符串@"bbb",那么这个问题就来了,这个操作实际上已经改变了原来的那片内存地址的值,那么会出现什么问题呢?没错,相当于strStrong所指向的内存地址的值也被修改了,所以strStrong对应内存中的值也变成了@"aaabbb"

如果你对指针和内存管理不熟悉的话,很容易会遇到这种难以发现的bug,因为站在你的角度来看,你只是改变了A,不会影响B,但实际上B已经被改变了。

那么如何防止这种问题的产生?

答:使用copy修饰
我们再来测试一下用copy修饰后的字符串,不可变的赋值我们就不测试了,因为原理跟上面的一样,我们来测试一下可变的赋值

测试三:copy修饰,赋值可变

NSMutableString *mutableStr = [NSMutableString stringWithString:@"aaa"];
self.strCopy = mutableStr;
[mutableStr appendString:@"bbb"];

经过LLDB调试可以看出,运行过了前两行代码,mutableStrstrCopy指向了不同的内存地址,指针变量也是不一样的

//运行完前两句代码
//NSMutableString *mutableStr = [NSMutableString stringWithString:@"aaa"];
//self.strCopy = mutableStr;
(lldb) p mutableStr
(__NSCFString *) $0 = 0x000060000271c0f0 @"aaa"
(lldb) p _strCopy
(NSTaggedPointerString *) $1 = 0x9c1cf86b18404957 @"aaa"
(lldb) p &mutableStr
(NSMutableString **) $2 = 0x00007ffee338a0e8
(lldb) p &_strCopy
(NSString **) $3 = 0x00007ff821d0a320

//运行完第三句代码
//[mutableStr appendString:@"bbb"];
(lldb) p mutableStr
(__NSCFString *) $4 = 0x000060000271c0f0 @"aaabbb"
(lldb) p _strCopy
(NSTaggedPointerString *) $5 = 0x9c1cf86b18404957 @"aaa"
(lldb) p &mutableStr
(NSMutableString **) $6 = 0x00007ffee338a0e8
(lldb) p &_strCopy
(NSString **) $7 = 0x00007ff821d0a320
  • 当给mutableStr追加完字符串之后,可以看出只有mutableStr改变了内存中的值,而strCopy仍然是@"aaa"
  • 这是因为用copy修饰strCopy属性,底层默认会在strCopysetter方法中对所赋值的可变字符串mutableStr先做一次copy操作然后再赋值,那么我们很容易能推出来这是一次深拷贝,会开辟新的内存地址,那么使得strCopy会指向新的内存地址,存放copy后的值
  • 那么就能得出mutableStr追加字符串并不会影响strCopy了,因为现在他们是两个不同的指针分别指向不同的内存地址

如果你已经看完这么详细(啰嗦:D)的一个分析,相信上面那个面试题就可以很好的回答出来了。

参考文章
面试题分解—「浅复制/深复制、定义属性使用copy还是strong ?

上一篇下一篇

猜你喜欢

热点阅读