OC的深拷贝与浅拷贝--NSArray与NSMutableArray应该使用copy还是strong?

Objective-C中对象的拷贝分为深拷贝和浅拷贝。另外还有容器类对象及非容器类对象的差别:
1. 对非容器类对象(如NSString、NSMutableString类对象)使用浅拷贝:拷贝的是对象的地址,没有新的内存被分配,只是原来的那块内容多了一个指针指向。也就是说新对象跟原对象都是指向的同一个内存地址,那么内容当然一样。
2. 对非容器类对象(如NSData、NSMutableData类对象)使用深拷贝:拷贝的是整个对象内容了,是通过给新对象分配了一块新的内存,然后将原对象对应内存中的内容一模一样在新的内存中写一份。所以内容是一样的,但是此时新对象与原对象的内存地址是不同的。
3. 对容器类对象(如NSArray、NSMutableArray类对象)使用浅拷贝:新的容器类对象也是指向的新的内存地址,但是容器内保存的对象没有进行拷贝,指向的内存地址还是和原容器对象内保存的对象指向的内存地址是一样的。也就是说你改了其中一个容器对象中的元素对象,那么另一个容器对象中的元素对象也会相应修改(是同一个内存地址嘛)。
4. 对容器类对象(如NSDictionary、NSMutableDictionary类对象)使用深拷贝:是需要对容器对象中的每一个元素都进行拷贝


**重点

在ARC下,我们是不可以对对象调用retain方法修改内存的引用计数的。我们需要先理解一下MRC下的retain、copy和mutableCopy的特点:

  1. retain:始终是浅拷贝,让新对象指针指向原对象,只是原来的内存地址多了一个指针指向,引用计数增加了1(但是系统会进行各种优化,不一定会加,像常量的引用计数就一直保持-1,不会变动,所以对常量string,进行retain也还是不会变)。返回对象是否可变与被复制的对象保持一致。(与ARC中的strong一样)
     
  2. copy:对于可变对象为深复制(开辟新内存,与原对象指向的不是一个对象了);对于不可变对象是浅复制(不开辟新内存,只是原内存地址加了一个新的指针指向,引用计数加1)。返回的对象始终是一个不可变对象。
     
  3. mutableCopy:始终是深复制(开辟新内存,与原来对象指向的内存空间不是同一处了)。返回的对象始终是一个可变对象。

retain始终是浅拷贝(内存地址指向的是同一处)

但是对应内存地址的引用计数却不一定会加1,大概因为系统会有各种优化吧?

比如:
当用initWithString或@“”这种形式创建NSString字符串时,如果判断到常量区有一个字符与这个字符匹配,那么系统不会再开辟内存创建多一个而是直接返回之前那个的地址。而且常量区内存里面的这个数据的引用计数总是-1,不会变化

copy对于不可变对象是浅拷贝(内存地址指向同一处,引用计数加1(常量区数据固定为-1),指向的是同一个对象)

copy对于可变对象是深拷贝(开辟新内存,已经不是指向同一个内存空间同一个对象了)

所以NSArray应该使用copy

// 不可变对象使用copy
@property(nonatomic, copy) NSArray *imutableArray;

就算是将可变对象使用setter方法赋值给它,也不怕因为原来的可变对象改变,造成新的对象也改变。因为,对可变对象使用copy方法赋值给新对象的时候,使用的是深拷贝(copy在可变对象调用的时候是深拷贝)。所以两个对象已经不是同一个了,你改了也不关我什么事。

如果是将不可变对象赋值给它的话,虽然这个时候进行的是浅拷贝,新指针指向的内存地址跟就对象是一样的(copy在不可变对象调用的时候是浅拷贝,大概是省得占用新内存的意思?),但是同样不用担心由于一个对象的改变,造成另一个对象也改变,因为原来的对象本来就是不可变对象嘛,另外新对象使用copy返回的,copy返回的都是不可变的对象呀。

但是NSArray使用strong修饰的话就可能有问题了

如果你打算声明一个不可变的NSArray,这样使用strong声明:

// 不可变对象使用copy
@property(nonatomic, strong) NSArray *array;

ARC里面的strong就类似MRC中retain是一样的。这样在setter方法赋值的时候,是使用浅拷贝的,也就是指针指向同一个地址,而且可变与不可变根据你用来赋值的对象而定的。

所以如果使用了下面的方式对这个strong的array赋值的话就会出问题:

NSMutableArray *mutableArray = [NSMutableArray array];
self.array = mutableArray;

这里我们是将一个NSMutableArray类型的,可变数组类型的对象赋值给了array属性。而且属性使用的是strong修饰。所以内部实现是:先保留新值(防止新旧值相同,先进行release的话,再retain会出错)、再release旧值、最后retain新值。
简单来说,最后的属性指向的是mutableArray这个对象的同一块地址。然而这块地址是可以通过修改mutableArray对象而修改的,这样就会造成array被同步修改。所以会问题的。

所以NSArray应该使用copy!

copy方法拷贝的,不管是可变对象还是不可变对象,最终返回的都是不可变的对象

NSMutableArray应该使用strong

如果ARC下有一个NSMutableArray属性是使用copy这样定义的:

@property(nonatomic, copy) NSMutableArray *mutableArray;

那么如果你在代码中这样调用set方法,那么会有如下的问题:

// 这样调用set方法
self.mutableArray = [NSMutableArray array];
// 随后这样调用
[self.mutableArray addObject:@"XXX"]; // 这里是会报错的,提示这个对象没有这个方法

这个就是我们前面讲过的,对可变数组进行了深复制,但是copy方法返回的始终是不可变的对象。也就是说self.mutableArray = [NSMutableArray array];这句代码在你使用copy声明属性的情况下,对应的set方法的实现其实是通过_mutableArray = [[NSMutableArray array] copy];这样一句语句实现的。所以你会发现我们定义的“可变的”数组,其实事实上是不可变的!所以自然也就不能调用addObject:的方法了!

所以,在需要可变数组属性可以改变的时候,需要使用strong修饰属性!

mutableCopy方法拷贝的,不管是可变对象还是不可变对象,最后返回的都是可变对象

比如,如下代码中的tempArray虽然声明的时候写的是NSArray的不可变类型,也是对不可变类型imutableArr执行的mutableCopy方法,但是最后得到的对象还是可以执行replaceObjectAtIndex:withObject:方法的.(当然直接调用是不能的,但是运行时调用还是可以的,最后看的是运行时的对象类型):

// 不可变类型
NSArray *imutableArr = [NSArray arrayWithObjects:@"111", @"222", @"333", nil];
// 调用mutableCopy方法,得到的是可变类型的(运行时就知道)
NSArray *tempArray = [imutableArr mutableCopy];

// 这里是调用运行时的objc_msgSend方法
((void (*) (id, SEL, NSUInteger, NSObject *)) objc_msgSend)(tempArray, @selector(replaceObjectAtIndex:withObject:), 0, @"XXXX");

同时这里也给了我们怎样使用performSelector传递多个参数(尤其是基础类型参数)的方法(直接调用底层的msgSend方法)。

可以参见博客资料,但是总感觉有的小地方写的不对

苹果官方:Collections Programming Topics – Copying Collections

这篇感觉写得很好,虽然还没看代码那块:
Objective-C中的深拷贝和浅拷贝

iOS 浅谈:深.浅拷贝与copy.strong – 汉斯哈哈哈

你可能感兴趣的:(iOS开发知识点)