NSArray与NSMutableArray应该使用copy还是strong?

先说标题的正确答案:

@property (nonatomic,copy) NSArray *immutableArray;

@property (nonatomic,strong) NSMutableArray *mutableArray;

其他集合:

NSArray,NSDictionary,NSSet -> copy
NSMutableArray,NSMutableDictionary,NSMutableSet->strong
``

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

**重点

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

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

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

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方法)。
 

讨论两个问题:
1. 使用strong修饰NSArray会有什么问题?
2. 使用copy修饰NSMutableArray会有什么问题?

1. 使用strong修饰NSArray的问题

#import <Foundation/Foundation.h>

@interface StrongCopyTest : NSObject

@property (nonatomic,strong) NSArray *immutableArray;

@property (nonatomic,strong) NSMutableArray *mutableArray;

- (void)doTest;

@end
#import "StrongCopyTest.h"

@implementation StrongCopyTest

- (void)doTest
{
    self.mutableArray = [NSMutableArray arrayWithObject:@"111"];
    self.immutableArray = [NSArray array];
    
    NSLog(@"before mutableArray=%@,immutableArray=%@",self.mutableArray,self.immutableArray);
    
    self.immutableArray = self.mutableArray;
    [self.mutableArray addObject:@"222"];
    
    NSLog(@"after mutableArray=%@,immutableArray=%@",self.mutableArray,self.immutableArray);
    
}
@end

输出结果如下

2018-05-14 17:28:15.872188+0800 StudyRuntime[861:40479] before mutableArray=(
    111
),immutableArray=(
)
2018-05-14 17:28:15.872308+0800 StudyRuntime[861:40479] after mutableArray=(
    111,
    222
),immutableArray=(
    111,
    222
)
Program ended with exit code: 0

可以看出,immutabeArray和mutableArray都被修改了,immutableArray不是预料的结果。

原因分析:
self.immutableArray = self.mutableArray;这行代码将可变数组的指针赋值给了不可变数组指针,由于OC中都是C的指针操作,并且是弱类型,因此这是合法的,编译器不会报错。甚至一个 id x = self.mutableArray也是可以的,因此弱类型是有潜在风险的。此时immutableArray和mutableArray都是指向了同一个内存地址,即mutableArray的内存地址,因此后面修改mutableArray的数据也会导致immutableArray的数据修改,因为是同一块内存地址。
这是一个陷阱。

2. 使用copy修饰NSMutableArray会有什么问题?

#import <Foundation/Foundation.h>

@interface StrongCopyTest : NSObject

//@property (nonatomic,copy) NSArray *immutableArray;

@property (nonatomic,copy) NSMutableArray *mutableArray;

- (void)doTest;

@end
#import "StrongCopyTest.h"

@implementation StrongCopyTest

- (void)doTest
{
    self.mutableArray = [NSMutableArray arrayWithObject:@"111"];
//    self.immutableArray = [NSArray array];
//    
//    NSLog(@"before mutableArray=%@,immutabxrleArray=%@",self.mutableArray,self.immutableArray);
//    
//    self.immutableArray = self.mutableArray;
    [self.mutableArray addObject:@"222"];
    
    //NSLog(@"after mutableArray=%@,immutableArray=%@",self.mutableArray,self.immutableArray);
    
}
@end

输出结果如下

2018-05-14 17:59:37.794905+0800 StudyRuntime[944:61946] -[__NSSingleObjectArrayI addObject:]: unrecognized selector sent to instance 0x10061c540
2018-05-14 17:59:37.796059+0800 StudyRuntime[944:61946] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSSingleObjectArrayI addObject:]: unrecognized selector sent to instance 0x10061c540'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff2a17032b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x00007fff517eac76 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff2a208e04 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
    3   CoreFoundation                      0x00007fff2a0e6870 ___forwarding___ + 1456
    4   CoreFoundation                      0x00007fff2a0e6238 _CF_forwarding_prep_0 + 120
    5   StudyRuntime                        0x0000000100001b91 -[StrongCopyTest doTest] + 161
    6   StudyRuntime                        0x0000000100001a85 testStrongCopy + 53
    7   StudyRuntime                        0x0000000100001ad5 main + 53
    8   libdyld.dylib                       0x00007fff52404015 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

原因分析:
self.mutableArray = [NSMutableArray arrayWithObject:@“111”];这句代码将得到一个不可变的数组(copy属性导致的).在此处打一个断点,然后输出其数据类型:

(lldb) po self.mutableArray
<__NSSingleObjectArrayI 0x102a01ed0>(
111
)

后面的异常就显而易见了。

self.mutableArray = [NSMutableArray arrayWithObject:@"111"];
这句话相当于

NSMutableArray *arr =  [NSMutableArray arrayWithObject:@"111"];
self.mutableArray = [arr copy];

 

猜你喜欢

转载自blog.csdn.net/ZhongLv_HoneyMoon/article/details/83786248
今日推荐