copy,strong,retain,weak和assign的区别
前言:
在初学iOS的时候,对于用什么关键词去修饰property,知其然而不知其所以然,大家都这么用,就这么用,不知道其原理。后来慢慢了解,看了大量的博客和自己慢慢的总结,摘抄了部分我理解的博客内容,内容基本属于前人总结,自己手动写一遍也是为了加强自身理解。
在知道他们的区别之前,我们首先要知道NSObject对象的赋值操作做了那些操作。A=C其实是在内存中创建一个A,然后又开辟了一个内存C,C里面存放的着值B。
示意图1
如下:
//给一个NSObject的变量A赋值B,我们其实是创建了一个指针A,然后指向了一个保存数据B的内存,这个内存地址是C。
NSMutableString *tempMStr = [[NSMutableStringalloc]initWithString:@"strValue"];
NSLog(@"tempMStr指针地址:%p,tempMStr值%@ tempMStr值引用计数%@\n", tempMStr,tempMStr,[tempMStr valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.842124+0800 lbCopy[2306:112462] tempMStr指针地址:0x600000057df0,tempMStr值strValue tempMStr值引用计数1
此处tempMStr就是A,值地址就是C,“strValue”就是B,而引用计数这个概念就是针对C的,赋值给其他变量或者指针设置为nil,如tempStr = nil,都会使得引用计数有所变化。当内存区域引用计数为0时就会将数据抹除。而我们使用copy,strong,retain,weak,assign区别就在于:此处tempMStr就是A,值地址就是C,“strValue”就是B,而引用计数这个概念就是针对C的,赋值给其他变量或者指针设置为nil,如tempStr = nil,都会使得引用计数有所变化。当内存区域引用计数为0时就会将数据抹除。
而我们使用copy,strong,retain,weak,assign区别就在于:
1.是否开辟新的内存
2.是否对地址C有所引用
需要注意的是property修饰符是在被赋值时起作用的
1.以典型的NSMutableString为例
@property (copy,nonatomic)NSMutableString *aCopyMStr;
@property (strong,nonatomic)NSMutableString *strongMStr;
@property (weak,nonatomic)NSMutableString *weakMStr;
@property (assign,nonatomic)NSMutableString *assignMStr;
//------------------可变变量实验------------------------"
NSMutableString *mstrOrigin = [[NSMutableStringalloc] initWithString:@"mstrOrigin in heap"];
self.aCopyMStr = mstrOrigin;
self.strongMStr = mstrOrigin;
self.weakMStr = mstrOrigin;
NSLog(@"mstrOrigin输出:指针地址%p,值地址%p,%@\n", &mstrOrigin,mstrOrigin,mstrOrigin);
//2018-01-22 10:13:52.842324+0800 lbCopy[2306:112462] mstrOrigin输出:指针地址0x7ffee8536ae0,值地址0x60c0000573a0,mstrOrigin in heap
NSLog(@"aCopyMStr输出:指针地址%p,值地址%p,%@\n", &_aCopyMStr,_aCopyMStr,_aCopyMStr);
//2018-01-22 10:13:52.842431+0800 lbCopy[2306:112462] aCopyMStr输出:指针地址0x7f99fea0af80,值地址0x60c0000492d0,mstrOrigin in heap
NSLog(@"strongMStr输出:指针地址%p,值地址%p,%@\n", &_strongMStr,_strongMStr,_strongMStr);
//2018-01-22 10:13:52.842564+0800 lbCopy[2306:112462] strongMStr输出:指针地址0x7f99fea0af88,值地址0x60c0000573a0,mstrOrigin in heap
NSLog(@"weakMStr输出:指针地址%p,值地址%p,%@\n", &_weakMStr,_weakMStr,_weakMStr);
//2018-01-22 10:13:52.842729+0800 lbCopy[2306:112462] weakMStr输出:指针地址0x7f99fea0af90,值地址0x60c0000573a0,mstrOrigin in heap
NSLog(@"引用计数%@",[mstrOriginvalueForKey:@"retainCount"]);
//2018-01-22 10:13:52.842870+0800 lbCopy[2306:112462] 引用计数2
NSLog(@"引用计数%@",[_weakMStrvalueForKey:@"retainCount"]);//?遗留问题,为什么此处_weakMStr是3
//2018-01-22 10:13:52.842989+0800 lbCopy[2306:112462] 引用计数3
//------------------结论------------------------"
//"除了copy修饰的aCopyMStr,strongMStr和weakMStr指针指向的内存地址都和mstrOrigin相同,但mstrOrigin内存引用计数为2,不为3,因为weakMStr不会增加指针指向的内存地址的计数指针。aCopyMStr赋值后则是自己单独在堆中开辟了一块内存,内存上保存“mstrOrigin”字符串,然后aCopyMStr指向了mstrOrigin");
strongMStr和weakMStr指针指向的内存地址都和mstrOrigin相同,但是mstrOrigin内存引用计数为2,不为3,因为weakMStr虽然指向了数据内存地址(之后用C简称,见示意图1),但不会增加C计数。copy修饰的aCopyMStr,赋值后则是自己单独开辟了一块内存,内存上保存“mstrOrigin”字符串,并指向。
拷贝示意图如下
NSMutableString拷贝示意图2
可见当我修改mstrOrigin的值的时候,必然不会影响aCopyMStr,只会影响strongMStr和weakMStr.我们来验证下
//------------------修改原值后------------------------"
[mstrOrigin appendString:@"1"];
NSLog(@"mstrOrigin输出:%p,%@\n", mstrOrigin,mstrOrigin);
//2018-01-22 10:13:52.843089+0800 lbCopy[2306:112462] mstrOrigin输出:0x60c0000573a0,mstrOrigin in heap1
NSLog(@"aCopyMStr输出:%p,%@\n",_aCopyMStr,_aCopyMStr);
//2018-01-22 10:13:52.843202+0800 lbCopy[2306:112462] aCopyMStr输出:0x60c0000492d0,mstrOrigin in heap
NSLog(@"strongMStr输出:%p,%@\n",_strongMStr,_strongMStr);
//2018-01-22 10:13:52.843305+0800 lbCopy[2306:112462] strongMStr输出:0x60c0000573a0,mstrOrigin in heap1
NSLog(@"weakMStr输出:%p,%@\n",_weakMStr,_weakMStr);
//2018-01-22 10:13:52.843418+0800 lbCopy[2306:112462] weakMStr输出:0x60c0000573a0,mstrOrigin in heap1
//------------------结论------------------------
//aCopyMStr值没有改变被修改,其他都随之修改,验证结论1"
copy会重新开辟新的内存来保存一份相同的数据。被赋值对象和原值修改互不影响。strong和weak赋值都指向原来数据地址,区别是前者会对数据地址进行引用计数+1,后者不会
引用计数是否+1有什么实质区别呢?
如果知道“值地址的引用计数为0时,地址上保存的值就会被释放”。那么区别就不难理解,weak修饰的指针A指向的值地址C,那么地址上当其他指向他的指针被释放的时候,这个值地址引用计数也就变为0了,这个A的值也就为nil了。换句话说当值地址C上没有其他强引用指针修饰的时候C就会被立即释放,A的值就变为nil了。
这里我们来初始化mstrOrigin和并将strongMStr设置为nil让C的引用计数为0,然后输出weakMStr,看是否为nil.
注:初始化和设为nil都可以将指针所指向的数据地址引用计数减少1
//------------------修改原指针和strongMStr的指向------------------------
mstrOrigin = [[NSMutableString alloc] initWithString:@"mstrOriginChange2"];
self.strongMStr =nil;
NSLog(@"mstrOrigin输出:%p,%@\n", mstrOrigin,mstrOrigin);
//2018-01-22 10:13:52.843535+0800 lbCopy[2306:112462] mstrOrigin输出:0x60c0000574c0,mstrOriginChange2
NSLog(@"strongMStr输出:%p,%@\n",_strongMStr,_strongMStr);
//2018-01-22 10:13:52.843649+0800 lbCopy[2306:112462] strongMStr输出:0x0,(null)
NSLog(@"weakMStr输出:%p,%@\n",_weakMStr,_weakMStr);
//2018-01-22 10:13:52.843769+0800 lbCopy[2306:112462] weakMStr输出:0x0,(null)
//------------------结论------------------------
//此时weakMStr没有值了,说明C随着strongMStr不再指向他,C被赋值nil,C的引用计数为0,证明结论2,3。
可见之前引用计数2是mstrOrigin和strongMStr添加的。
结论:copy会重新开辟新的内存来保存一份相同的数据。被赋值对象和原值修改互不影响。strong和weak虽然都指向原来数据地址,原值修改的时候storng和weak会随之变化。区别是前者会对数据地址进行引用计数+1防止原地址值被释放,但后者不会,当其他值都不在指向值地址时,值地址被释放,weak的值也就是为nil了。我们称会对数据地址增加引用计数的为强引用,不改变引用计数的为弱引用
1.2 assign和weak的区别
对assign和weak修饰的值进行赋值,并输出指针结构地址和值
self.assignMStr = mstrOrigin;
self.weakMStr = mstrOrigin;
NSLog(@"mstrOrigin输出:%p,%@\n", mstrOrigin,mstrOrigin);
//2018-01-22 10:13:52.843874+0800 lbCopy[2306:112462] mstrOrigin输出:0x60c0000574c0,mstrOriginChange2
NSLog(@"weakMStr输出:%p,%@\n",_weakMStr,_weakMStr);
//2018-01-22 10:13:52.906916+0800 lbCopy[2306:112462] weakMStr输出:0x60c0000574c0,mstrOriginChange2
NSLog(@"assignMStr输出:%p,%@\n",_assignMStr,_assignMStr);
//2018-01-22 10:13:52.907085+0800 lbCopy[2306:112462] assignMStr输出:0x60c0000574c0,mstrOriginChange2
mstrOrigin = [[NSMutableString alloc] initWithString:@"mstrOriginChange3"];
NSLog(@"mstrOrigin输出:%p,%@\n", mstrOrigin,mstrOrigin);
//2018-01-22 10:13:52.907248+0800 lbCopy[2306:112462] mstrOrigin输出:0x60800005b900,mstrOriginChange3
NSLog(@"weakMStr输出:%p,%@\n",_weakMStr,_weakMStr);
//2018-01-22 10:13:52.907377+0800 lbCopy[2306:112462] weakMStr输出:0x0,(null)
//NSLog(@"assignMStr输出:%p,%@\n", _assignMStr,_assignMStr);
//------------------结论------------------------"
//可以当mstrOrigin重新初始化后,assignMStr输出偶先奔溃,不奔溃的时候也存在值,且值不为nil。证明结论4"
可以发现在输出assignMStr时会偶尔出现奔溃的情况。原因是发送了野指针的情况。assign同weak,指向C并且计数不+1,但当C地址引用计数为0时,assign不会对C地址进行B数据的抹除操作,只是进行值释放。这就导致野指针存在,即当这块地址还没写上其他值前,能输出正常值,但一旦重新写上数据,该指针随时可能没有值,造成奔溃。
1.3那retain是什么
ARC之前属性构造器的关键字是retain,copy,assign,strong和weak是ARC带出来的关键字。
retain现在同strong,就是指针指向值地址,同时进行引用计数加1。
2.非NSMutableString的情况
上面我们讨论了典型的例子NSMutableString,即非容器可变变量。也就是说还存在其他三种类型需要讨论...
1.非容器不可变变量NSSting
2.容器可变变量NSMutableArray
3.容器不可变变量NSArray
更重要的是不同类型会有不同结果...,好吧,不要奔溃,上面一大段我们讨论了1/4,接下来我们要讨论其他的3/4情况。但好消息是,其他几种情况基本与上面非容器可变变量情况基本类似。
2.1容器可变变量
容器可变变量的典型例子就是NSMutableArray
下面代码可以忽略,只做参考用
@property (copy,nonatomic)NSMutableArray *aCopyMArr;
@property (strong,nonatomic)NSMutableArray *strongMArr;
@property (weak,nonatomic)NSMutableArray *weakMArr;
@property (assign,nonatomic)NSMutableArray *assignMArr;
//3.可变容器变量,如NSMutableArray。容器本身和可变非容器变量一样。但容器中的数据不管是copy和weak,还是strong都是浅拷贝
NSMutableArray *mArrOrigin = [[NSMutableArrayalloc] init];
NSLog(@"mArrOrigin中的数据引用计数%@", [mArrOrigin valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.910967+0800 lbCopy[2306:112462] mArrOrigin中的数据引用计数(
// )
NSMutableString *mstr1 = [[NSMutableStringalloc] initWithString:@"value1"];
NSMutableString *mstr2 = [[NSMutableStringalloc] initWithString:@"value2"];
NSMutableString *mstr3 = [[NSMutableStringalloc] initWithString:@"value3"];
[mArrOrigin addObject:mstr1];
[mArrOrigin addObject:mstr2];
NSLog(@"mArrOrigin中的数据引用计数%@", [mArrOrigin valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.911164+0800 lbCopy[2306:112462] mArrOrigin中的数据引用计数(
// 2,
// 2
// )
self.aCopyMArr = mArrOrigin;
self.strongMArr = mArrOrigin;
self.weakMArr = mArrOrigin;
NSLog(@"mArrOrigin输出:%p,%@\n", mArrOrigin,mArrOrigin);
//2018-01-22 10:13:52.911319+0800 lbCopy[2306:112462] mArrOrigin输出:0x60800005b9c0,(
// value1,
// value2
// )
NSLog(@"aCopyMArr输出:%p,%@\n",_aCopyMArr,_aCopyMArr);
//2018-01-22 10:13:52.911443+0800 lbCopy[2306:112462] aCopyMArr输出:0x60400003bb60,(
// value1,
// value2
// )
NSLog(@"strongMArr输出:%p,%@\n",_strongMArr,_strongMArr);
//2018-01-22 10:13:52.911683+0800 lbCopy[2306:112462] strongMArr输出:0x60800005b9c0,(
// value1,
// value2
// )
NSLog(@"weakMArr输出:%p,%@\n",_weakMArr,_weakMArr);
//2018-01-22 10:13:52.911904+0800 lbCopy[2306:112462] weakMArr输出:0x60800005b9c0,(
// value1,
// value2
// )
NSLog(@"weakMArr输出:%p,%@\n",_weakMArr[0],_weakMArr[0]);
//2018-01-22 10:13:52.912126+0800 lbCopy[2306:112462] weakMArr输出:0x6040002470e0,value1
NSLog(@"mArrOrigin中的数据引用计数%@", [mArrOrigin valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.912316+0800 lbCopy[2306:112462] mArrOrigin中的数据引用计数(
// 3,
// 3
// )
NSLog(@"%p %p %p %p",&mArrOrigin,mArrOrigin,mArrOrigin[0],mArrOrigin[1]);
//2018-01-22 10:13:52.912530+0800 lbCopy[2306:112462] 0x7ffee8536ad0 0x60800005b9c0 0x6040002470e0 0x604000246f30
[_weakMArr addObject:mstr3];
NSLog(@"mArrOrigin输出:%p,%@\n" , mArrOrigin,mArrOrigin);
//2018-01-22 10:13:52.912789+0800 lbCopy[2306:112462] mArrOrigin输出:0x60800005b9c0,(
// value1,
// value2,
// value3
// )
NSLog(@"aCopyMArr输出:%p,%@\n",_aCopyMArr,_aCopyMArr);
//2018-01-22 10:13:52.912980+0800 lbCopy[2306:112462] aCopyMArr输出:0x60400003bb60,(
// value1,
// value2
// )
NSLog(@"strongMArr输出:%p,%@\n",_strongMArr,_strongMArr);
//2018-01-22 10:13:52.913203+0800 lbCopy[2306:112462] strongMArr输出:0x60800005b9c0,(
// value1,
// value2,
// value3
// )
NSLog(@"weakMArr输出:%p,%@\n",_weakMArr,_weakMArr);
//2018-01-22 10:13:52.913500+0800 lbCopy[2306:112462] weakMArr输出:0x60800005b9c0,(
// value1,
// value2,
// value3
// )
NSLog(@"mArrOrigin中的数据引用计数%@", [mArrOrigin valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.913702+0800 lbCopy[2306:112462] mArrOrigin中的数据引用计数(
// 3,
// 3,
// 2
// )
NSLog(@"-------aCopyMArr输出:%p,%@ %@\n", mstr1,mstr1,[mstr1valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.913886+0800 lbCopy[2306:112462] -------aCopyMArr输出:0x6040002470e0,value1 3
[mstr1 appendFormat:@"aaa"];
NSLog(@"mArrOrigin输出:%p,%@\n" , mArrOrigin,mArrOrigin);
//2018-01-22 10:13:52.914100+0800 lbCopy[2306:112462] mArrOrigin输出:0x60800005b9c0,(
// value1aaa,
// value2,
// value3
// )
NSLog(@"aCopyMArr输出:%p,%@\n",_aCopyMArr,_aCopyMArr);
//2018-01-22 10:13:52.914348+0800 lbCopy[2306:112462] aCopyMArr输出:0x60400003bb60,(
// value1aaa,
// value2
// )
NSLog(@"strongMArr输出:%p,%@\n",_strongMArr,_strongMArr);
//2018-01-22 10:13:52.914651+0800 lbCopy[2306:112462] strongMArr输出:0x60800005b9c0,(
// value1aaa,
// value2,
// value3
// )
NSLog(@"weakMArr输出:%p,%@\n",_weakMArr,_weakMArr);
//2018-01-22 10:13:52.914872+0800 lbCopy[2306:112462] weakMArr输出:0x60800005b9c0,(
// value1aaa,
// value2,
// value3
// )
NSLog(@"-------aCopyMArr输出:%p,%@ %@\n", mstr1,mstr1,[mstr1valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.915270+0800 lbCopy[2306:112462] -------aCopyMArr输出:0x6040002470e0,value1aaa 3
//------------------结论------------------------"
//发现除了CopyMArr其他数组地址都一样,也就是说除了copy其他都是指针指向C而已,也就是浅拷贝。修改容器本身,只对浅拷贝的值影响,不对copy的值影响
NSLog(@"mArrOrigin中的第一个数据输出:%p,%@\n", mArrOrigin[0],mArrOrigin[0]);
//2018-01-22 10:13:52.915447+0800 lbCopy[2306:112462] mArrOrigin中的第一个数据输出:0x6040002470e0,value1aaa
NSLog(@"aCopyMArr中的第一个数据输出:%p,%@\n", _aCopyMArr[0],_aCopyMArr[0]);
//2018-01-22 10:13:52.915642+0800 lbCopy[2306:112462] aCopyMArr中的第一个数据输出:0x6040002470e0,value1aaa
NSLog(@"strongMArr中的第一个数据输出:%p,%@\n", _strongMArr[0],_strongMArr[0]);
//2018-01-22 10:13:52.915858+0800 lbCopy[2306:112462] strongMArr中的第一个数据输出:0x6040002470e0,value1aaa
NSLog(@"weakMArr中的第一个数据输出:%p,%@\n", _weakMArr[0],_weakMArr[0]);
//2018-01-22 10:13:52.916065+0800 lbCopy[2306:112462] weakMArr中的第一个数据输出:0x6040002470e0,value1aaa
[mstr1 appendFormat:@"change"];
NSLog(@"mArrOrigin中的第一个数据输出:%p,%@\n", mArrOrigin[0],mArrOrigin[0]);
//2018-01-22 10:13:52.916261+0800 lbCopy[2306:112462] mArrOrigin中的第一个数据输出:0x6040002470e0,value1aaachange
NSLog(@"aCopyMArr中的第一个数据输出:%p,%@\n", _aCopyMArr[0],_aCopyMArr[0]);
//2018-01-22 10:13:52.916437+0800 lbCopy[2306:112462] aCopyMArr中的第一个数据输出:0x6040002470e0,value1aaachange
NSLog(@"strongMArr中的第一个数据输出:%p,%@\n", _strongMArr[0],_strongMArr[0]);
//2018-01-22 10:13:52.916641+0800 lbCopy[2306:112462] strongMArr中的第一个数据输出:0x6040002470e0,value1aaachange
NSLog(@"weakMArr中的第一个数据输出:%p,%@\n", _weakMArr[0],_weakMArr[0]);
//2018-01-22 10:13:52.916958+0800 lbCopy[2306:112462] weakMArr中的第一个数据输出:0x6040002470e0,value1aaachange
//------------------结论------------------------
//容器本身会因为不同修饰符进行有深浅拷贝,但容器中的值地址都是同一个,修改后被赋值的数组都会修改。
mArrOrigin = nil;
self.aCopyMArr =nil;
self.strongMArr =nil;
NSLog(@"mArrOrigin中的第一个数据输出:%p,%@\n", mArrOrigin[0],mArrOrigin[0]);
//2018-01-22 10:13:52.917199+0800 lbCopy[2306:112462] mArrOrigin中的第一个数据输出:0x0,(null)
NSLog(@"aCopyMArr中的第一个数据输出:%p,%@\n", _aCopyMArr[0],_aCopyMArr[0]);
//2018-01-22 10:13:52.917363+0800 lbCopy[2306:112462] aCopyMArr中的第一个数据输出:0x0,(null)
NSLog(@"strongMArr中的第一个数据输出:%p,%@\n", _strongMArr[0],_strongMArr[0]);
//2018-01-22 10:13:52.917646+0800 lbCopy[2306:112462] strongMArr中的第一个数据输出:0x0,(null)
NSLog(@"weakMArr中的第一个数据输出:%p,%@\n", _weakMArr[0],_weakMArr[0]);
//2018-01-22 10:13:52.917928+0800 lbCopy[2306:112462] weakMArr中的第一个数据输出:0x0,(null)
//------------------结论------------------------"
//当aCopyMArr和strongMArr为nil时,也就是说C只有被weakMArr连接的时候,规则和可变非容器变量,不可变非容器变量一样,weakMarr也会被释放"
//4.不可变非容器变量,如NSArray。容器本身和不可变非容器变量一样,但容器内的值永远是浅拷贝,自行试验
// 总结
// copy,strong,weak,assign的区别。
// 可变变量中,copy是重新开辟一个内存,strong,weak,assgin后三者不开辟内存,只是指针指向原来保存值的内存的位置,storng指向后会对该内存引用计数+1,而weak,assgin不会。weak,assgin会在引用保存值的内存引用计数为0的时候值为空,并且weak会将内存值设为nil,assign不会,assign在内存没有被重写前依旧可以输出,但一旦被重写将出现奔溃
// 不可变变量中,因为值本身不可被改变,copy没必要开辟出一块内存存放和原来内存一模一样的值,所以内存管理系统默认都是浅拷贝。其他地方和可变变量一样,如weak修饰的变量同样会在内存引用计数为0时变为nil。
// 容器本身遵守上面准则,但容器内部的每个值都是浅拷贝。
// 综上所述,当创建property构造器创建变量value1的时候,使用copy,strong,weak,assign根据具体使用情况来决定。value1 = value2,如果你希望value1和value2的修改不会互相影响的就用用copy,反之用strong,weak,assign。如果你还希望原来值C为nil的时候,你的变量不为nil就用strong,反之用weak和assign。
// 实际应用,不同页面之间值如果希望同时被修改就用strong,如果同时修改但希望不强引用就是用weak,如果只是拿这个值就用copy。数组拷贝的时候要注意,里面元素不会深拷贝,如果希望强拷贝
上面代码有点多,所做的操作是mArrOrigin(value1,value2)赋值给copy,strong,weak修饰的aCopyMArr,strongMArr,weakMArr。通过给原数组增加元素,修改原数组元素值,然后输出mArrOrigin的引用计数,和数组地址,查看变化。发现其中数组本身指向的内存地址除了aCopyMArr重新开辟了一块地址,strongMArr,weakMArr和mArrOrigin指针指向的地址是一样的。也就是说
容器可变变量中容器本身和非容器可变变量是一样的,copy深拷贝,strongMArr,weakMArr和assign都是浅拷贝
另外我们发现被拷贝对象mArrOrigin中的数据引用计数居然不是1而是3。也就是说容器内的数据拷贝都是进行了浅拷贝。同时当我们修改数组中的一个数据时strongMArr,weakMArr,aCopyMArr中的数据都改变了,说明容器可变变量中的数据在拷贝的时候都是浅拷贝
容器可变变量的拷贝结构如下图
NSMutableArray拷贝示意图3
2.2非容器不变变量
典型例子是NSString
我们还是以代码引出结果
//2.不可变非容器变量NSString
//不可变量因为变量本身不会变,所以其实任何赋值都没必要重新创建一个块内存,在这个不可变的内存中存放一样的值,节省内存的方式是都指向同一个地方。所以不可变量的拷贝都是浅拷贝,但依旧遵循一个原则就是copy和strong存在时,C依旧不会被释放。但只有weak时值依旧会被释放
//1.copy会不会开辟内存地址,指针指向C,引用计数加1
//2.strong指针指向C,引用计数不加1。
//3.weak指针指向C,引用计数不加1。
//4.assign同weak
//dohere nsstring的copy和strong以及assgin的区别不能通过引用计数来讲,因为nsstring是存在放在_TEXT段的,而引用计数是放在堆内存中的一个整型,对象alloc开辟堆内存空间后,引用计数自动置1;
//_TEXT段:整个程序的代码,以及所有的常量。这部分内存是是固定大小的,只读的。了解_TEXT段内存管理方式,来分析copy ,strong,assgin的区别。
//------------------不可变量实验------------------------"
NSString *strOrigin = [[NSStringalloc] initWithUTF8String:"strOrigin0123456"];
NSLog(@"strOrigin值内存引用计数%@\n", [_strongStrvalueForKey:@"retainCount"]);
//2018-01-22 10:13:52.907515+0800 lbCopy[2306:112462] strOrigin值内存引用计数(null)
self.aCopyStr = strOrigin;
NSLog(@"strOrigin值内存引用计数%@\n", [_strongStrvalueForKey:@"retainCount"]);
//2018-01-22 10:13:52.907636+0800 lbCopy[2306:112462] strOrigin值内存引用计数(null)
self.strongStr = strOrigin;
NSLog(@"strOrigin值内存引用计数%@\n", [_strongStrvalueForKey:@"retainCount"]);
//2018-01-22 10:13:52.907773+0800 lbCopy[2306:112462] strOrigin值内存引用计数3
NSLog(@"strOrigin值内存引用计数%@\n", [strOrigin valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.907905+0800 lbCopy[2306:112462] strOrigin值内存引用计数3
self.weakStr = strOrigin;
NSLog(@"strOrigin输出:值地址%p,指针地址%p,%@\n", strOrigin,&strOrigin,strOrigin);
//2018-01-22 10:13:52.908019+0800 lbCopy[2306:112462] strOrigin输出:值地址0x60c0000574c0,指针地址0x7ffee8536ad8,strOrigin0123456
NSLog(@"aCopyStr输出:值地址%p,指针地址%p,%@\n", _aCopyStr,&_aCopyStr,_aCopyStr);
//2018-01-22 10:13:52.908205+0800 lbCopy[2306:112462] aCopyStr输出:值地址0x60c0000574c0,指针地址0x7f99fea0afa0,strOrigin0123456
NSLog(@"strongStr输出:值地址%p,指针地址%p,%@\n", _strongStr,&_strongStr,_strongStr);
//2018-01-22 10:13:52.908329+0800 lbCopy[2306:112462] strongStr输出:值地址0x60c0000574c0,指针地址0x7f99fea0afa8,strOrigin0123456
NSLog(@"weakStr输出:值地址%p,指针地址%p,%@\n", _weakStr,&_weakStr,_weakStr);
//2018-01-22 10:13:52.908525+0800 lbCopy[2306:112462] weakStr输出:值地址0x60c0000574c0,指针地址0x7f99fea0afb0,strOrigin0123456
NSLog(@"strOrigin值内存引用计数%@\n", [_strongStrvalueForKey:@"retainCount"]);
//2018-01-22 10:13:52.908681+0800 lbCopy[2306:112462] strOrigin值内存引用计数3
//------------------修改原值后------------------------"
strOrigin = [[NSString alloc] initWithUTF8String:"aaa"];
NSLog(@"strOrigin输出:%p,%@\n", strOrigin,strOrigin);
//2018-01-22 10:13:52.908825+0800 lbCopy[2306:112462] strOrigin输出:0xa000000006161613,aaa
NSLog(@"aCopyStr输出:%p,%@\n",_aCopyStr,_aCopyStr);
//2018-01-22 10:13:52.908977+0800 lbCopy[2306:112462] aCopyStr输出:0x60c0000574c0,strOrigin0123456
NSLog(@"strongStr输出:%p,%@\n",_strongStr,_strongStr);
//2018-01-22 10:13:52.909098+0800 lbCopy[2306:112462] strongStr输出:0x60c0000574c0,strOrigin0123456
NSLog(@"weakStr输出:%p,%@\n",_weakStr,_weakStr);
//2018-01-22 10:13:52.909676+0800 lbCopy[2306:112462] weakStr输出:0x60c0000574c0,strOrigin0123456
//------------------结论------------------------
//strOrigin值被改变,其他指针指向没有变化。说明不可变类型值不可被修改,只能被重新初始化"
self.aCopyStr =nil;
self.strongStr =nil;
NSLog(@"strOrigin输出:%p,%@\n", strOrigin,strOrigin);
//2018-01-22 10:13:52.909878+0800 lbCopy[2306:112462] strOrigin输出:0xa000000006161613,aaa
NSLog(@"aCopyStr输出:%p,%@\n",_aCopyStr,_aCopyStr);
//2018-01-22 10:13:52.910286+0800 lbCopy[2306:112462] aCopyStr输出:0x0,(null)
NSLog(@"strongStr输出:%p,%@\n",_strongStr,_strongStr);
//2018-01-22 10:13:52.910488+0800 lbCopy[2306:112462] strongStr输出:0x0,(null)
NSLog(@"weakStr输出:%p,%@\n",_weakStr,_weakStr);
//2018-01-22 10:13:52.910705+0800 lbCopy[2306:112462] weakStr输出:0x0,(null)
//------------------结论------------------------
//当只有weakStr拥有C时,值依旧会被释放,同非容器可变变量
综合上面现象NSString和NSMutableString(非容器可变变量)基本相同,除了copy,NSString为浅拷贝,NSMutableString是深拷贝。那么为什么NSString的copy是浅拷贝呢,也就是说为什么aCopyStr不自己开辟一个独立的内存出来呢。答案很简单,因为不可变量的值不会改变,既然都不会改变,所以没必要重新开辟一个内存出来让aCopyStr指向他,直接指向原来值位置就可以了。示意图如下
所以非容器不可变量除了copy其他特性同非容器可变变量,copy是浅拷贝
2.3不可变容器变量
//------------------不可变量实验NSArray------------------------
NSArray *arrayOrigin = [NSArrayarrayWithObjects:@"value1",@"value2",nil];
NSLog(@"arrayOrigin值内存引用计数%@\n", [arrayOrigin valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.918315+0800 lbCopy[2306:112462] arrayOrigin值内存引用计数(
// 18446744073709551615,
// 18446744073709551615
// )
self.aCopyArr = arrayOrigin;
NSLog(@"arrayOrigin值内存引用计数%@\n", [arrayOrigin valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.918580+0800 lbCopy[2306:112462] arrayOrigin值内存引用计数(
// 18446744073709551615,
// 18446744073709551615
// )
self.strongArr = arrayOrigin;
NSLog(@"strongArr值内存引用计数%@\n", [_strongArrvalueForKey:@"retainCount"]);
//2018-01-22 10:13:52.918746+0800 lbCopy[2306:112462] strongArr值内存引用计数(
// 18446744073709551615,
// 18446744073709551615
// )
NSLog(@"arrayOrigin值内存引用计数%@\n", [arrayOrigin valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.918928+0800 lbCopy[2306:112462] arrayOrigin值内存引用计数(
// 18446744073709551615,
// 18446744073709551615
// )
self.weakArr = arrayOrigin;
NSLog(@"arrayOrigin输出:值地址%p,指针地址%p,%@\n", arrayOrigin,&arrayOrigin,arrayOrigin);
//2018-01-22 10:13:52.919154+0800 lbCopy[2306:112462] arrayOrigin输出:值地址0x608000039480,指针地址0x7ffee8536ab0,(
// value1,
// value2
// )
NSLog(@"aCopyArr输出:值地址%p,指针地址%p,%@\n", _aCopyArr,&_aCopyArr,_aCopyArr);
//2018-01-22 10:13:52.919354+0800 lbCopy[2306:112462] aCopyArr输出:值地址0x608000039480,指针地址0x7f99fea0afe0,(
// value1,
// value2
// )
NSLog(@"strongArr输出:值地址%p,指针地址%p,%@\n", _strongArr,&_strongArr,_strongArr);
//2018-01-22 10:13:52.919596+0800 lbCopy[2306:112462] strongArr输出:值地址0x608000039480,指针地址0x7f99fea0afe8,(
// value1,
// value2
// )
NSLog(@"weakArr输出:值地址%p,指针地址%p,%@\n", _weakArr,&_weakArr,_weakArr);
//2018-01-22 10:13:52.919788+0800 lbCopy[2306:112462] weakArr输出:值地址0x608000039480,指针地址0x7f99fea0aff0,(
// value1,
// value2
// )
NSLog(@"arrayOrigin值内存引用计数%@\n", [arrayOrigin valueForKey:@"retainCount"]);
//2018-01-22 10:13:52.920010+0800 lbCopy[2306:112462] arrayOrigin值内存引用计数(
// 18446744073709551615,
// 18446744073709551615
// )
NSLog(@"arrayOrigin中的第一个数据输出:%p,%@\n", arrayOrigin[0],arrayOrigin[0]);
//2018-01-22 10:13:52.920200+0800 lbCopy[2306:112462] arrayOrigin中的第一个数据输出:0x1076cc3f0,value1
NSLog(@"aCopyArr中的第一个数据输出:%p,%@\n", _aCopyArr[0],_aCopyArr[0]);
//2018-01-22 10:13:52.920388+0800 lbCopy[2306:112462] aCopyArr中的第一个数据输出:0x1076cc3f0,value1
NSLog(@"strongArr中的第一个数据输出:%p,%@\n", _strongArr[0],_strongArr[0]);
//2018-01-22 10:13:52.920579+0800 lbCopy[2306:112462] strongArr中的第一个数据输出:0x1076cc3f0,value1
NSLog(@"weakArr中的第一个数据输出:%p,%@\n", _weakArr[0],_weakArr[0]);
//2018-01-22 10:13:52.920810+0800 lbCopy[2306:112462] weakArr中的第一个数据输出:0x1076cc3f0,value1
//------------------修改原值后------------------------
arrayOrigin = [NSArrayarrayWithObjects:@"value11111",@"value22222",nil];
NSLog(@"arrayOrigin输出:%p,%@\n", arrayOrigin,arrayOrigin);
//2018-01-22 10:13:52.921018+0800 lbCopy[2306:112462] arrayOrigin输出:0x600000037d00,(
// value11111,
// value22222
// )
NSLog(@"aCopyArr输出:%p,%@\n",_aCopyArr,_aCopyArr);
//2018-01-22 10:13:52.921214+0800 lbCopy[2306:112462] aCopyArr输出:0x608000039480,(
// value1,
// value2
// )
NSLog(@"strongArr输出:%p,%@\n",_strongArr,_strongArr);
//2018-01-22 10:13:52.921445+0800 lbCopy[2306:112462] strongArr输出:0x608000039480,(
// value1,
// value2
// )
NSLog(@"weakArr输出:%p,%@\n",_weakArr,_weakArr);
//2018-01-22 10:13:52.921625+0800 lbCopy[2306:112462] weakArr输出:0x608000039480,(
// value1,
// value2
// )
NSLog(@"arrayOrigin中的第一个数据输出:%p,%@\n", arrayOrigin[0],arrayOrigin[0]);
//2018-01-22 10:13:52.921806+0800 lbCopy[2306:112462] arrayOrigin中的第一个数据输出:0x1076cc710,value11111
NSLog(@"aCopyArr中的第一个数据输出:%p,%@\n", _aCopyArr[0],_aCopyArr[0]);
//2018-01-22 10:13:52.921976+0800 lbCopy[2306:112462] aCopyArr中的第一个数据输出:0x1076cc3f0,value1
NSLog(@"strongArr中的第一个数据输出:%p,%@\n", _strongArr[0],_strongArr[0]);
//2018-01-22 10:13:52.922484+0800 lbCopy[2306:112462] strongArr中的第一个数据输出:0x1076cc3f0,value1
NSLog(@"weakArr中的第一个数据输出:%p,%@\n", _weakArr[0],_weakArr[0]);
//2018-01-22 10:13:52.923258+0800 lbCopy[2306:112462] weakArr中的第一个数据输出:0x1076cc3f0,value1
//------------------结论------------------------
//arrayOrigin值被改变,其他指针指向没有变化。说明不可变类型值不可被修改,只能被重新初始化
self.aCopyArr =nil;
self.strongArr =nil;
NSLog(@"arrayOrigin输出:%p,%@\n", arrayOrigin,arrayOrigin);
//2018-01-22 10:13:52.923495+0800 lbCopy[2306:112462] arrayOrigin输出:0x600000037d00,(
// value11111,
// value22222
// )
NSLog(@"aCopyArr输出:%p,%@\n",_aCopyArr,_aCopyArr);
//2018-01-22 10:13:52.923885+0800 lbCopy[2306:112462] aCopyArr输出:0x0,(null)
NSLog(@"strongArr输出:%p,%@\n",_strongArr,_strongArr);
//2018-01-22 10:13:52.924110+0800 lbCopy[2306:112462] strongArr输出:0x0,(null)
NSLog(@"weakArr输出:%p,%@\n",_weakArr,_weakArr);
//2018-01-22 10:13:52.924592+0800 lbCopy[2306:112462] weakArr输出:0x608000039480,(
// value1,
// value2
// )
NSLog(@"arrayOrigin中的第一个数据输出:%p,%@\n", arrayOrigin[0],arrayOrigin[0]);
//2018-01-22 10:13:52.924851+0800 lbCopy[2306:112462] arrayOrigin中的第一个数据输出:0x1076cc710,value11111
NSLog(@"aCopyArr中的第一个数据输出:%p,%@\n", _aCopyArr[0],_aCopyArr[0]);
//2018-01-22 10:13:52.925057+0800 lbCopy[2306:112462] aCopyArr中的第一个数据输出:0x0,(null)
NSLog(@"strongArr中的第一个数据输出:%p,%@\n", _strongArr[0],_strongArr[0]);
//2018-01-22 10:13:52.925264+0800 lbCopy[2306:112462] strongArr中的第一个数据输出:0x0,(null)
NSLog(@"weakArr中的第一个数据输出:%p,%@\n", _weakArr[0],_weakArr[0]);
//2018-01-22 10:13:52.926248+0800 lbCopy[2306:112462] weakArr中的第一个数据输出:0x1076cc3f0,value1
//------------------结论------------------------
//当只有weakArr拥有C时,值依旧会被释放,同非容器可变变量
典型对象NSArray。该对象实验自行实验。但结论在这里给出,其实不实验也可以大概知道概率
在不可变容器变量中,容器本身都是浅拷贝包括copy,同NSString,容器里面的数据都是浅拷贝,同NSMutableArray。
3.总结
copy,strong,weak,assign的区别。
可变变量中,copy是重新开辟一个内存,strong,weak,assgin后三者不开辟内存,只是指针指向原来保存值的内存的位置,storng指向后会对该内存引用计数+1,而weak,assgin不会。weak,assgin会在引用保存值的内存引用计数为0的时候值为空,并且weak会将内存值设为nil,assign不会,assign在内存没有被重写前依旧可以输出,但一旦被重写将出现奔溃。
不可变变量中,因为值本身不可被改变,copy没必要开辟出一块内存存放和原来内存一模一样的值,所以内存管理系统默认都是浅拷贝。其他和可变变量一样,如weak修饰的变量同样会在内存引用计数为0时变为nil。
容器本身遵守上面准则,但容器内部的每个值都是浅拷贝。
**综上所述,当创建property构造器创建变量value1的时候,使用copy,strong,weak,assign根据具体使用情况来决定。value1 = value2,如果你希望value1和value2的修改不会互相影响的就用用copy,反之用strong,weak,assign。如果你还希望原来值C(C是什么见示意图1)为nil的时候,你的变量不为nil就用strong,反之用weak和assign。weak和assign保证了不强引用某一块内存,如delegate我们就用weak表示,就是为了防止循环引用的产生。
另外,我们上面讨论的是类变量,直接创建局部变量默认是Strong修饰