2024-06-23 06:34:36
在开发中,我们经常说copy用来修饰block或者nsstring字符串。那么究竟为什么要用copy而不用strong呢?
首先我们做一个测试:上图
这时候我们看到strong修饰的strongStr只想原来的地址,而copy修饰的copysStr指向了新的内存地址,所以str改变,strongStr随着改变,而copysStr并没有影响。
当然很多人看到copy都会想到深拷贝和浅拷贝的问题了,关于这个问题我在上一篇播客中已经写过了并附上的有测试demo: https://www.jianshu.com/writer#/notebooks/24598196/notes/42429579
了解copy和mutablecopy之后,会有人质疑:不是说copy一个不可变对象为浅拷贝吗?这里copy修饰的是nsstring,是一个不可变对象啊,为什么变成了深拷贝呢?按理说应该是copysstr也应该随str改变啊?
接下来我们继续研究:上图
同样,copy修饰的是一个不可变对象nsstring,这里我们赋值时给予一个不可变对象nsstring,那么就不一样了。先分析第一段,从打印来看,strong修饰的strongStr和copy修饰的copysStr都是指向str的内存地址。因此这里copy修饰的不可变对象copysStr成为了浅拷贝。
分析第二段,这里我们str进行了修改,看到str的打印地址已经变了。本来str属于一个不可变字符串,我们之所以能修改,是因为str=“ddd”相当于开辟了一个新的内存地址,str指针指向了ddd的地址,而并非是修改了原来str指向hello的那个内存地址。接着往下看,由于第一段测试得出test.strongStr和test.copysStr都是浅拷贝,因此依然指向原来为hello的地址,str指向新地址并不会改变strongStr和copysStr的值,这里打印的地址正好也印证了这一点。
我们是不是可以认为copy修饰符虽然是修饰了属性,但是否为深浅拷贝跟属性类型无关,而是和赋值的对象类型有关呢?
带着这个疑惑我们继续探究,上图:
这里我们用copy修饰了一个可变数组array,我们给array赋值的也是一个可变数组temparray。上篇博客我们已经验证了,无论可变还是不可变,copy出来的都是不可变。可变对象的拷贝属于深拷贝。这里我们观察发现,self.array指向的是一个新的内存地址,并不是temparray的,因此temparray添加新元素并不会影响self.array。从报错信息来看,我们无法给内存地址为0x600002afbfc0的数组添加元素,说明self.array是不可变数组。到这里,我们只剩下最后一点疑惑了,self.array到底是temparray拷贝过来的还是array拷贝过来的呢?
我始终相信实践是检验真理的唯一标准。
接着上图:
这里我们把一个不可变数组赋给了一个可变数组,本身写法有误,但是为了调试并不影响。从打印地址来看,这时self.array和temparray指向了同一个地址0x6000032c7700。而当我们对self.array进行元素添加时发生了报错。到这里全部真相大白了,说明我们的copy修饰的array进行了浅拷贝,我们知道只有当不可变对象使用copy时才是浅拷贝,那我们copy修饰的是一个可变数组,赋值的是一个不可变数组。
因此证明了属性的修饰符是和赋值对象的类型有关系的,而不是和当前的对象类型有关系
我们在开发中经常是定义什么类型的对象,就会赋值什么类型的对象,所以误以为了修饰符就只和当前属性的类型有关系
最后问题出在哪呢?其实不管是刚才对字符串的验证还是对数组的验证,所有的问题都出在点语法上,我们知道sefl.array其实就是走了一个set赋值方法(_array = array),而array就是temparray作为参数传过去的指针,因此set方法赋值其实就是temparray来copy一个指针,具体有没有创建新的内存地址就要看temparray的类型了。
这里我们就很好解释为什么nsstring经常用copy了,因为当copy修饰之后,我们的属性赋值一个可变字符串时,会被深拷贝成不可变字符串,这样原字符串即使发生变动也不会影响我们的属性值,保证了属性的稳定性。
如果赋值一个不可变字符串时,又正好是浅拷贝。而不可变字符串无法发生变动,就上上边的str=@“ddd”,貌似发生了变动,其实对于不可变字符串,这样写就是开辟新的内存地址,str又指向了新的地址,而原来的地址依然被我们的copysStr指着,并不会销毁,也不会改变,也保证了属性的稳定性。