本篇文章的完善版本:对属性变量赋新值时可能引发的血案
今天review同事的代码,发现一个bug,如下代码:
_dataArr = newArr;
其中_dataArr是属性变量,newArr是局部变量,于是问了如下的几个问题:
1.掉用属性使用 __ 与 self. 的区别(不是会调用get方法这么简单)。
2.修饰数据对象属性时使用copy与strong的区别。(不是深拷贝与浅拷贝这么简单)
3.给属性(比如可变数组)赋值时采用“=”赋值与setArray的区别。
提出后发现对该部分基础掌握不是很熟悉,所以决定分享下对于初级开发时常常疑惑而出错的这些地方,于是写了如下这个测试demo分别演示以上问题所导致的结果,注意观察局部变量的变化给属性变量的数据与地址在各种情况下的影响。
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *mArrStrong;
@property (nonatomic, copy) NSMutableArray *mArrCopy;
@property (nonatomic, strong) NSMutableArray *mArrStrong2;
@property (nonatomic, copy) NSMutableArray *mArrCopy2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//_mArrStrong = [NSMutableArray array];
//_mArrCopy = [NSMutableArray array];
_mArrStrong2 = [NSMutableArray array];
_mArrCopy2 = [NSMutableArray array];
[self test1];
[self test2];
}
- (void)test1
{
NSLog(@"%s", __func__);
NSMutableArray *arr1 = [NSMutableArray arrayWithObjects:@"1", nil];
_mArrStrong = arr1;
_mArrCopy = arr1;
[arr1 addObject:@"2"];
NSLog(@"%p", arr1);
NSLog(@"a:%p %@", _mArrStrong, _mArrStrong);
NSLog(@"b:%p %@", _mArrCopy, _mArrCopy);
arr1 = (NSMutableArray *)@[@"000"];
NSLog(@"额外测试:arr1:%p _mArrStrong:%p _mArrCopy:%p %@ \n%@", arr1, _mArrStrong, _mArrCopy, _mArrStrong, _mArrCopy);
NSMutableArray *arr2 = [NSMutableArray arrayWithObjects:@"1", nil];
self.mArrStrong = arr2;
self.mArrCopy = arr2;
[arr2 addObject:@"2"];
NSLog(@"%p", arr2);
NSLog(@"c:%p %@", self.mArrStrong, self.mArrStrong);
NSLog(@"d:%p %@", self.mArrCopy, self.mArrCopy);
[arr2 setArray:@[@"000"]];
NSLog(@"额外测试2:arr2:%p self.mArrStrong:%p self.mArrCopy:%p %@ \n%@", arr2, self.mArrStrong, self.mArrCopy, self.mArrStrong, self.mArrCopy);
}
- (void)test2
{
NSLog(@"%s", __func__);
NSMutableArray *arr1 = [NSMutableArray arrayWithObjects:@"1", nil];
[_mArrStrong2 setArray:arr1];
[_mArrCopy2 setArray:arr1];
[arr1 addObject:@"2"];
NSLog(@"%p", arr1);
NSLog(@"e:%p %@", _mArrStrong2, _mArrStrong2);
NSLog(@"f:%p %@", _mArrCopy2, _mArrCopy2);
NSMutableArray *arr2 = [NSMutableArray arrayWithObjects:@"1", nil];
[self.mArrStrong2 setArray:arr2];
[self.mArrCopy2 setArray:arr2];
[arr2 addObject:@"2"];
NSLog(@"%p", arr2);
NSLog(@"g:%p %@", self.mArrStrong2, self.mArrStrong2);
NSLog(@"h:%p %@", self.mArrCopy2, self.mArrCopy2);
}
@end
输出如下:
2016-10-13 19:29:29.018 testArr[40794:1137604] -[ViewController test1]
2016-10-13 19:29:29.018 testArr[40794:1137604] 0x7f89cac0f3b0
2016-10-13 19:29:29.018 testArr[40794:1137604] a:0x7f89cac0f3b0 (1, 2)
2016-10-13 19:29:29.019 testArr[40794:1137604] b:0x7f89cac0f3b0 (1, 2)
2016-10-13 19:29:29.019 testArr[40794:1137604] 额外测试:arr1:0x7f89cd000f00 _mArrStrong:0x7f89cac0f3b0 _mArrCopy:0x7f89cac0f3b0 (1, 2) (1, 2)
2016-10-13 19:29:29.019 testArr[40794:1137604] 0x7f89cd00c5d0
2016-10-13 19:29:29.019 testArr[40794:1137604] c:0x7f89cd00c5d0 (1, 2)
2016-10-13 19:29:29.019 testArr[40794:1137604] d:0x7f89cd000f20 (1)
2016-10-13 19:29:29.019 testArr[40794:1137604] 额外测试2:arr2: 0x7f89cd00c5d0 self.mArrStrong: 0x7f89cd00c5d0 self.mArrCopy: 0x7f89cd000f20 (000) (1)
2016-10-13 19:29:29.019 testArr[40794:1137604] -[ViewController test2]
2016-10-13 19:29:29.019 testArr[40794:1137604] 0x7f89cd014250
2016-10-13 19:29:29.019 testArr[40794:1137604] e:0x7f89cae19c10 (1)
2016-10-13 19:29:29.019 testArr[40794:1137604] f:0x7f89cae19b40 (1)
2016-10-13 19:29:29.020 testArr[40794:1137604] 0x7f89cac16e40
2016-10-13 19:29:29.020 testArr[40794:1137604] g:0x7f89cae19c10 (1)
2016-10-13 19:29:29.034 testArr[40794:1137604] h:0x7f89cae19b40 (1)
这样的输出如果你不觉得意外,那么不用继续浏览下文了。下面仔细谈下我的理解。
代码解析:
-
输出的a与b:
是通过 _ 获取的属性变量,他等同于一个全局变量,所以对于他的修饰copy或strong都不管作用,然后是通过指针赋值引用arr1的地址,从而_mArrStrong和_mArrCopy与arr1指向同一个内存地址,拥有一样的值。所以_mArrStrong和_mArrCopy会由于arr1的变化而变化。
-
输出的额外测试1:
arr1指向了一个新的内存地址,数据是@[@"000"],而他之前指向的内存还有_mArrStrong和_mArrCopy引用故不会被释放,所以arr1改变了,但是不会影响_mArrStrong与_mArrCopy的地址和数据还是保持之前的。
-
输出的c与d:
是通过self. 获取的属性变量,他会在返回 _ 变量前掉用get方法,从而与修饰的copy或strong构成了关联。若是strong修饰的,则是浅拷贝,地址与值都保持一致,只是多了一个引用;反之若是copy修饰的,则是深拷贝,获得了一份新的值,但是数据一样。所以arr2的变化会引起self.mArrStrong的变化,而不会改变self.mArrCopy。
-
输出的额外测试2:
arr2指向的地址的值改变了,引起指向同一地址的self.mArrStrong的值也改变了,与self.mArrCopy无关。
-
输出的e与f:
是通过setArray方法赋值,_mArrStrong2与_mArrCopy2的地址不会变化,还是指向初始化时指向的地址,但是拥有了新的值为arr1,之后arr1指向的地址的值发生变化也不会联系到_mArrStrong2与_mArrCopy2。
-
输出的g与h:
虽然self.mArrStrong2是浅拷贝,但是通过setArray赋了新值,地址还是初始化的地址,因此不会随着arr2的变化而变化,self.mArrCopy2同理。
-
特别提醒:
输出的d,实现的是深拷贝功能,我并没有给self.mArrCopy申请新的内存,但是系统会自动给它申请。那么是何时申请的呢?就在调用self.mArrCopy时系统会判断给它new内存,因此我们在初始化时别用 self.mArrCopy = [NSMutableArray array];这样的方式初始化,因为执行self.mArrCopy时 mArrCopy 为nil,系统会自动new内存, 但是 = 后面又开辟了内存给self.mArrCopy引用,从而导致内存浪费。因此在初始化强引用的属性对象时用 _ 引用进行初始化,如:_mArrStrong = [NSMutableArray array];
总结如下:
只有采用self.的方式获取copy修饰的属性时才会是深拷贝,若是用setArray或是初始化方法赋值时则与修饰无关。无论面向可变的还是不可变的数据对象,包括数组、字符串、字典等都具有一样的性质。若有不恰当之处请指出。
因此我的惯用写法是使用copy修饰数据对象,更新数据时用setArray或是初始化方法。这样做更保险,通过自己管理自己的生命周期,不依赖其它变量。
本篇文章的完善版本:对属性变量赋新值时可能引发的血案