看下面一段简单而且非常常见的代码片段,如果和dataArr相关的代码不是正如下面的这样,那么结果将会有什么不同吗?答案是将会有可能产生非常严重的后果,下面来一一演示下。
@property (nonatomic, copy) NSMutableArray *dataArr;
_dataArr = [NSMutableArray array];
- (void)refreshData:(NSMutableArray *)newArr
{
[self.dataArr setArray: newArr];
......
}
为了模拟实际开发中会遇到的问题,准备了如下完整的测试代码:
@interface ViewController ()
@property (nonatomic, copy) NSMutableArray *dataArr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_dataArr = [NSMutableArray array];
NSMutableArray *mArr = [NSMutableArray arrayWithObjects:@"1", nil];
[self refreshData:mArr];
[mArr addObject:@"3"];
}
- (void)refreshData:(NSMutableArray *)newArr
{
[self.dataArr setArray:newArr];
// self.dataArr = newArr;
// _dataArr = newArr;
NSLog(@"%p %p", _dataArr, newArr);
__block ViewController* bSelf = self;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[bSelf.dataArr addObject:@"2"];
NSLog(@"%@", bSelf.dataArr);
});
}
@end
测试:
1.以上代码输出结果是:
1,2
这是正常的代码方式,dataArr只是使用了newArr的值,不会再随着newArr的变化而变化,拥有自己的可变地址空间。
2.将[self.dataArr setArray:newArr]
改为self.dataArr = newArr
:
运行结果将会崩在 [bSelf.dataArr addObject:@"2"];
这一行。
这样是实现了深拷贝的功能,但是拷贝出来的类型是不可变的,导致无法调用addObject方法。
3.将[self.dataArr setArray:newArr]
改为_dataArr = newArr
:
输出结果是:
1,3,2
_dataArr引用了newArr的地址,导致_dataArr会随着newArr值的变化而变化。
4.将修饰dataArr的copy改为strong:
输出结果是:
1,2
因此用setArray:更新数据时,无论copy还是strong修饰的属性都与临时变量newArr无关,拥有自己的内存地址。
5.将修饰dataArr的copy改为strong,同时将[self.dataArr setArray:newArr]
改为self.dataArr = newArr
:
输出结果是:
1,3,2
因为采用的strong为浅拷贝,所以共同引用newArr的地址,导致_dataArr会随着newArr值的变化而变化。
6.将修饰dataArr的copy改为strong,同时将[self.dataArr setArray:newArr]
改为_dataArr = newArr
:
输出结果是:
1,3,2
_dataArr指针指向了newArr指向的地址,这与用什么修饰的dataArr无关了。
7.将mArr初始化为不可变数组,并且将[self.dataArr setArray:newArr]
改为self.dataArr = newArr
,再注释掉报错的[mArr addObject:@"3"]
:
通过打印的内存地址可以看到self.dataArr 与 newArr地址一样,并没有实现深拷贝,这与上面测试2中形成了反例。
8.将声明属性dataArr的NSMutableArray改为NSArray、NSString、 NSMutableString、NSDictionary、NSMutableDictionary
也会有以上同样的测试结果。
</br>
结论:
1.由以上的代码与输出结果可以看出,对于会接收新值的数据对象请初始化为可变数据对象,并使用[self.dataArr setArray:newArr]或[_dataArr setArray:newArr]这样的方式赋与新值,如上测试1输出时的代码方式,不然可能会导致崩溃或不稳定性。
2.如果属性非要使用不可变数据对象(不推荐),则最好用self.dataArr = [NSArray arrayWithArray:newArr]这样的方式赋值初始化属性变量,这样会造成频繁申请内存(同时也会在释放)。
3.纠正一下普遍说法,“ copy修饰不可变对象为浅拷贝,copy修饰可变对象为深拷贝”,从上面测试7中可说明这个说法不成立。
补充:
使用属性与全局变量的区别:
1.属性便于在别的类中调用与赋值,可添加只读与只写修饰;
2.使用属性时,可以重写set与get方法实现一些功能;
2.全局变量可以用static关键字修饰,生命周期和程序相同,只有在此类中可见;
3.全局变量可以用extern关键字修饰给整个项目共享数据(必须保证此变量名在此项目中唯一);
4.全局变量多了一个可以设置protected的访问权限,但是它们都可实现private与public的访问权限,默认声明在.h中即是public,声明在.m中即是private的;
5.它们存储区不一样,全局变量存储在全局区静态区。
因此,在那些特殊情况下除外尽量使用属性,并且声明在.m中,在别的类中需要调用时,将声明再剪切到.h中即可。