我觉得我这个人什么都好,就是有时候有点懒 ,搞了三年多iOS开发了,回头一看网上都没有留下自己的痕迹,昨天跟一位CTO聊天,讲到职业发展的道路,他跟我说,作为一个技术人员,要懂得分享,要学会传播自己的知识,而不能掖着藏着,你在分享的过程中,不仅自己对知识的理解更到位,还能扩大自己在圈子里的影响力,走上更好的职业发展之路,听完他的话,我觉得我浪费了几年美好的光阴,所以我决定慢慢的把自己懂得东西分享出来,跟大家一起交流一起进步,如果有讲的不好的地方,希望大家多多斧正。
下面进入今天的主题,一直以来不断的有人问我关于内存管理方面的内容,我也回答了很多,今天我就将内存管理方面的一些重要的东西拆成小的模块逐一的讲解下,今天要讲的是字符串里面为什么要用copy来修饰。 请看下面的代码截图:我定义了四个属性,分别用strong来修饰的NSString和用copy来修饰的NSString,这样会形成一个对比,方便大家理解
@property(nonatomic, strong)NSString *testString;
@property(nonatomic, strong)NSMutableString *test2String;
@property(nonatomic, copy)NSString *test3String;
@property(nonatomic, strong)NSMutableString *test4String;
定义好属性后我们对其进行赋值操作 :
self.testString = @"aaaa";
self.test2String = [[NSMutableString alloc] init];
self.test2String.string = @"nuli";
self.testString = _test2String;
self.test3String = @"bbb";
NSMutableString *test4String = [[NSMutableString alloc] init];
self.test4String = test4String;
self.test4String.string = [@"fendou" mutableCopy];
self.test3String = _test4String;
我们首先给testString进行了赋值,然后生成了一个test2String,并对其赋值,然后分别再来看testString和test2String的内存地址和值,我们通过打断点使用lldb来查看,当程序执行到断点处的时候
![2.png](http://upload-images.jianshu.io/upload_images/4316454-ec471757b6404b7b.png? imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
当我们将test2String赋值给testString之后内存地址和值如下图所示:
可以看到两个字符串的值和内存一样,这个时候我们来改变test2String字符串的值然后在来查看:
我们会发现两个字符串的值同时发生了改变,但内存地址是一致的,所以我们可以肯定两个字符串是共用的一份内存,也就是说使用strong来修饰NSString,在你对其进行赋值的时候,只是复制了一个指针而已,并没有分配新的内存空间,这样的话会导致一个什么样的问题呢?就是你在程序的其他地方更改了test2String的值后testString的值也会发生改变,所以这个会存在潜在的数据污染的风险,如非明确共用一份内存外,不建议使用strong来修饰NSString
下面我们来看看使用copy修饰的字符串赋值的时候是怎么样的,请看下图:当程序执行到这个地方的时候:
将test4String的值赋给test3String之后内存地址和值如下图所示:
可以看到test3String字符串的内存和内容都没有发生改变,改变test3String 的值test4String的地址和内容也没有发生任何改变,所有我们可以知道使用copy关键字修饰的字符串在赋值的时候会重新生成一块新的内存,然后把另一个字符串的内容复制进来,这就相当于两个字符串都存在于两个独立的内存空间,所以改变任何一个字符串的值对另外一个字符串都不会有任何的影响,这个也就是我们所说的深复制。
修饰数组时其实也一样,如下面,我定义了两个可变数组:
@property(nonatomic,copy,nullable)NSMutableArray<YCCombineModel *> *historyOrderArray;
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *arr = [NSMutableArray array];
_historyOrderArray = a;
for (int i = 0; i<20; i++) {
YCCombineModel *model = [[YCCombineModel alloc] init];
model.isSelected = NO;
[_historyOrderArray addObject:model];
}
}
当我用copy修饰数组的时候,代码执行到
[_historyOrderArray addObject:model];
这行代码的时候会挂掉,要理解崩溃的原因先要知道一些常识性的东西:
在iOS开发中,对象之间传值都是使用引用计数器增加的方式,这种方式的适用于当对象的某属性的值改变时,引用该对象的不同指针会同时改变,因为这两个指针指向的是同一个内存地址,当一个指针执行的对象属性值发生改变时,不影响另一个对象,那么需要分配两个不同的内存地址,也就是说,我们就不可以采用retain关键字了,而是要采用copy 关键字。如:
property (nonatomic,copy)NSSting * name;
一:
区分深复制与浅复制,一般只有可变的mutableCopy到mutableCopy的Copy才是浅复制,不产生副本只是retain count增加。
副本的特点:彼此的内容一样,拥有相同的方法,在内存中有两个对象.
1.copy
1> 如果对象有 可变/不可变 版本的区别, copy方法只能拷贝出不可变的副本.
2> 如果对象没有 可变/不可变 版本的区别,copy方法只是建立一个副本.
2.mutableCopy
建立对象的可变版本的副本(如果对象有 可变/不可变 版本之分)
3.深拷贝/浅拷贝
1>深拷贝:如果建立出新的副本,在内存中有两个对象.就是深拷贝.
可变 -> 不可变 (copy)
可变 -> 可变 (mutableCopy)
不可变 -> 可变 (mutableCopy)
2.浅拷贝:不会建立新的副本, 只是引用计数加1.
不可变 -> 不可变 (因为两个不可变对象谁都不会被改变,也就没必要建立副本)
因为定义了copy修饰属性后 在执行其setter方法的时候会先release掉旧值然后retain 新值,并且上面那种赋值实际就相当于 _historyOrderArray = [arr copy]; copy后就会返回一个NSArray类型的对象。所以调用addObject方法就会报unrecognized selector sent to instance 0x14e14970错误, 但是如果使用strong来修饰_historyOrderArray就相当于直接赋值,不会报错。
看完两个的对比之后,我相信你对字符串和数组的修饰符的使用有了一个新的认识。感谢您花费宝贵的时间来阅读此文!如果有错误的话欢迎大家多多指点,当然有想深入交流的也可以加我微信:bubiandeai05