assign、weak、retain、strong、copy、_
assign
用于对基本数据类型进行赋值操作,不更改引用计数。也可以用来修饰对象,但是,被assign修饰的对象在释放后,指针的地址还是存在的,也就是说指针并没有被置为nil,成为野指针。如果后续在分配对象到堆上的某块内存时,正好分到这块地址,程序就会crash。之所以可以修饰基本数据类型,因为基本数据类型一般分配在栈上,栈的内存会由系统自动处理,不会造成野指针。
weak
修饰Object类型,修饰的对象在释放后,指针地址会被置为nil,是一种弱引用。在ARC环境下,为避免循环引用,往往会把delegate属性用weak修饰;在MRC下使用assign修饰。weak和strong不同的是:当一个对象不再有strong类型的指针指向它的时候,它就会被释放,即使还有weak型指针指向它,那么这些weak型指针也将被清除。
weak 与 assgin 的区别
assigin 可以用非 OC 对象,而 weak 必须用于 OC 对象
strong
ARC下的strong等同于MRC下的retain都会把对象引用计数加1。
copy
会在内存里拷贝一份对象,两个指针指向不同的内存地址。一般用来修饰NSString等有对应可变类型的对象,因为他们有可能和对应的可变类型(NSMutableString)之间进行赋值操作,为确保对象中的字符串不被修改 ,应该在设置属性是拷贝一份。而若用strong修饰,如果对象在外部被修改了,会影响到属性。
strong与copy的区别
Copy
,Strong
的区别需要了解点内存管理的知识,Strong是ARC下引入的修饰,相当于手动管理内存(MRC)下的retain,在相关代码下,常常看到有的人用copy修饰NSString,NSArray,NSDictionary等存在可变与不可变之分的对象,常常会用copy,而不是strong,下面代码来解释一下strong与copy的区别:
先说明一下什么叫做浅拷贝,什么叫做深拷贝;
浅Copy:可以理解为指针的复制,只是多了一个指向这块内存的指针,共用一块内存。
深Copy:理解为内存的复制,两块内存是完全不同的,也就是两个对象指针分别指向不同的内存,互不干涉。
举例
首先在类延展中声明两个属性变量
@property (nonatomic, strong)NSString * stringStrong; //strong修饰的字符串对象
@property (nonatomic, copy)NSString * stringCopy; //copy修饰的字符串对象
接着创建两个不可变字符串(NSString)
//新创建两个NSString对象
NSString * strong1 = @"I am Strong!";
NSString * copy1 = @"I am Copy!";
将两个属性分别进行赋值
//初始化两个字符串
self.stringStrong = strong1;
self.stringCopy = copy1;
分别打印一下四个变量的内存地址:
NSLog(@"strong1 = %p",strong1);
NSLog(@"stringStrong = %p",self.stringStrong);
NSLog(@"copy1 = %p",copy1);
NSLog(@"stringCopy = %p",self.stringCopy);
结果如下:可以看出,此时无论是strong修饰的字符串还是copy修饰的字符串,都进行了浅Copy。
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] strong1 = 0x10a0b3078
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] stringStrong = 0x10a0b3078
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] copy1 = 0x10a0b3098
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] stringCopy = 0x10a0b3098
如果创建两个可变字符串对象(NSMutableString)
//新创建两个NSMutableString对象
NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"];
NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"];
分别对属性再次进行赋值
self.stringStrong = mutableStrong;
self.stringCopy = mutableCopy;
分别打印一下四个变量的地址:结果如下:这时就发现了,用strong修饰的字符串依旧进行了浅Copy,而由copy修饰的字符串进行了深Copy,所以mutableStrong与stringStrong指向了同一块内存,而mutableCopy和stringCopy指向的是完全两块不同的内存。
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] mutableStrong = 0x7fccba425d60
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] stringStrong = 0x7fccba425d60
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] mutableCopy = 0x7fccba40d7c0
2018-03-11 18:59:06.333 StrongOrCopy[5046:421886] stringCopy = 0x7fccba4149e0
那么有什么用呢,实例来看一下有什么区别:
首先是对不可变字符串进行操作:
//新创建两个NSString对象
NSString * strong1 = @"I am Strong!";
NSString * copy1 = @"I am Copy!";
//初始化两个字符串
self.stringStrong = strong1;
self.stringCopy = copy1;
//两个NSString进行操作
[strong1 stringByAppendingString:@"11111"];
[copy1 stringByAppendingString:@"22222"];
分别对在字符串后面进行拼接,当然这个拼接对原字符串没有任何的影响,因为不可变自字符串调用的方法都是有返回值的,原来的值是不会发生变化的。打印如下,对结果没有任何的影响:
2018-03-11 19:15:26.729 StrongOrCopy[5146:439360] strong1 = I am Strong!
2018-03-11 19:15:26.729 StrongOrCopy[5146:439360] stringStrong = I am Strong!
2018-03-11 19:15:26.729 StrongOrCopy[5146:439360] copy1 = I am Copy!
2018-03-11 19:15:26.729 StrongOrCopy[5146:439360] stringCopy = I am Copy!
然后是对可变字符串进行操作:
//新创建两个NSMutableString对象
NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"];
NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"];
//初始化两个字符串
self.stringStrong = mutableStrong;
self.stringCopy = mutableCopy;
//两个MutableString进行操作
[mutableStrong appendString:@"Strong!"];
[mutableCopy appendString:@"Copy!"];
再来看一下结果:对mutableStrong进行的操作,由于用strong修饰的stringStrong没有进行深Copy,导致共用了一块内存,当mutableStrong对内存进行了操作的时候,实际上对stringStrong也进行了操作; 相反,用copy修饰的stringCopy进行了深Copy,也就是说stringCopy与mutableCopy用了两块完全不同的内存,所以不管mutableCopy进行了怎么样的变化,原来的stringCopy都不会发生变化。这就在日常中避免了出现一些不可预计的错误。
2018-03-11 19:20:27.652 StrongOrCopy[5245:446189] stringStrong = StrongMutableStrong!
2018-03-11 19:20:27.652 StrongOrCopy[5245:446189] mutableStrong = StrongMutableStrong!
2018-03-11 19:20:27.652 StrongOrCopy[5245:446189] stringCopy = CopyMutable
2018-03-11 19:20:27.652 StrongOrCopy[5245:446189] mutableCopy = CopyMutableCopy!
总结
在不可变对象之间进行转换,strong与copy作用是一样的,但如果在不可变与可变之间进行操作,strong与copy就不同了。
__weak
作为程序猿还是代码具有说服力,上栗子:
首先定义一个类 MyObject 继承 NSObject,并添加一个属性 text,重写了description方法,返回 text 的值。这个主要是因为编译器本身对 NSString 是有优化的,创建的 string 对象有可能是静态存储区永不释放的,为了避免使用 NSString 引起一些问题,还是创建一个 NSObject 对象比较合适。
自定义了一个 ZXLog 方法输出对象相关值,定义如下:
#define ZXLog(prefix,Obj) {NSLog(@"变量内存地址:%p, 变量值:%p, 指向对象值:%@, --> %@",&Obj,Obj,Obj,prefix);}
代码:
MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
ZXLog(@"obj", obj);
__weak MyObject *weakObj = obj;
ZXLog(@"weakObj", weakObj);
void(^testBlock)(void) = ^(){
ZXLog(@"weakObj - block", weakObj);
};
testBlock();
obj = nil;
testBlock();
打印结果:
变量内存地址:0x7fff510b7c78, 变量值:0x60000001a270, 指向对象值:<MyObject: 0x60000001a270>, --> obj
变量内存地址:0x7fff510b7c70, 变量值:0x60000001a270, 指向对象值:<MyObject: 0x60000001a270>, --> weakObj
变量内存地址:0x60400044cfe0, 变量值:0x60000001a270, 指向对象值:<MyObject: 0x60000001a270>, --> weakObj - block
变量内存地址:0x60400044cfe0, 变量值:0x0, 指向对象值:(null), --> weakObj - block
从上面的结果可以看到
-
block 内的 weakObj 和外部的 weakObj 并不是同一个变量
-
block 捕获了 weakObj 同时也是对 obj 进行了弱引用,当我在 block 外把 obj 释放了之后,block 内也读不到这个变量了
-
当 obj 赋值 nil 时,block 内部的 weakObj 也为 nil 了,也就是说 obj 实际上是被释放了,可见 __weak 是可以避免循环引用问题的
接下来看第二段代码:
MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
ZXLog(@"obj", obj);
__weak MyObject *weakObj = obj;
ZXLog(@"weakObj-0", weakObj);
void(^testBlock)(void) = ^(){
__strong MyObject *strongObj = weakObj;
ZXLog(@"weakObj - block", weakObj);
ZXLog(@"strongObj - block", strongObj);
};
ZXLog(@"weakObj-1", weakObj);
testBlock();
ZXLog(@"weakObj-2", weakObj);
obj = nil;
testBlock();
ZXLog(@"weakObj-3", weakObj);
打印结果:
变量内存地址:0x7fff517bcc78, 变量值:0x60400000a420, 指向对象值:<MyObject: 0x60400000a420>, --> obj
变量内存地址:0x7fff517bcc70, 变量值:0x60400000a420, 指向对象值:<MyObject: 0x60400000a420>, --> weakObj-0
变量内存地址:0x7fff517bcc70, 变量值:0x60400000a420, 指向对象值:<MyObject: 0x60400000a420>, --> weakObj-1
变量内存地址:0x600000259df0, 变量值:0x60400000a420, 指向对象值:<MyObject: 0x60400000a420>, --> weakObj - block
变量内存地址:0x7fff517bcb28, 变量值:0x60400000a420, 指向对象值:<MyObject: 0x60400000a420>, --> strongObj - block
变量内存地址:0x7fff517bcc70, 变量值:0x60400000a420, 指向对象值:<MyObject: 0x60400000a420>, --> weakObj-2
变量内存地址:0x600000259df0, 变量值:0x0, 指向对象值:(null), --> weakObj - block
变量内存地址:0x7fff517bcb28, 变量值:0x0, 指向对象值:(null), --> strongObj - block
变量内存地址:0x7fff517bcc70, 变量值:0x0, 指向对象值:(null), --> weakObj-3
如果你看过 AFNetworking 的源码,会发现 AFN 中作者会把变量在 block 外面先用 __weak 声明,在 block 内把前面 weak 声明的变量赋值给 __strong 修饰的变量这种写法。
从上面例子我们看到即使在 block 内部用 strong 强引用了外面的 weakObj ,但是一旦 obj 释放了之后,内部的 strongObj 同样会变成 nil,那么这种写法又有什么意义呢?
下面再看一段代码:
MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
ZXLog(@"obj", obj);
__weak MyObject *weakObj = obj;
ZXLog(@"weakObj-0", weakObj);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong MyObject *strongObj = weakObj;
ZXLog(@"weakObj - block", weakObj);
ZXLog(@"strongObj - block", strongObj);
sleep(3);
ZXLog(@"weakObj - block", weakObj);
ZXLog(@"strongObj - block", strongObj);
});
NSLog(@"------ sleep 1s");
sleep(1);
obj = nil;
ZXLog(@"weakObj-1", weakObj);
NSLog(@"------ sleep 5s");
sleep(5);
ZXLog(@"weakObj-2", weakObj);
打印结果:
变量内存地址:0x7fff5f891c78, 变量值:0x6000000133f0, 指向对象值:<MyObject: 0x6000000133f0>, --> obj
变量内存地址:0x7fff5f891c70, 变量值:0x6000000133f0, 指向对象值:<MyObject: 0x6000000133f0>, --> weakObj-0
------ sleep 1s
变量内存地址:0x60000025a2a0, 变量值:0x6000000133f0, 指向对象值:<MyObject: 0x6000000133f0>, --> weakObj - block
变量内存地址:0x700000722d78, 变量值:0x6000000133f0, 指向对象值:<MyObject: 0x6000000133f0>, --> strongObj - block
变量内存地址:0x7fff5f891c70, 变量值:0x6000000133f0, 指向对象值:<MyObject: 0x6000000133f0>, --> weakObj-1
------ sleep 5s
变量内存地址:0x60000025a2a0, 变量值:0x6000000133f0, 指向对象值:<MyObject: 0x6000000133f0>, --> weakObj - block
变量内存地址:0x700000722d78, 变量值:0x6000000133f0, 指向对象值:<MyObject: 0x6000000133f0>, --> strongObj - block
变量内存地址:0x7fff5f891c70, 变量值:0x0, 指向对象值:(null), --> weakObj-2
代码中使用 sleep 来保证代码执行的先后顺序。
从结果中我们可以看到,只要 block 部分执行了,即使我们中途释放了 obj,block 内部依然会继续强引用它。对比上面代码,也就是说 block 内部的 __strong 会在执行期间进行强引用操作,保证在 block 内部 strongObj 始终是可用的。这种写法非常巧妙,既避免了循环引用的问题,又可以在 block 内部持有该变量。
综合两部分代码,我们平时在使用时,常常先判断 strongObj 是否为空,然后再执行后续代码,如下方式:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong MyObject *strongObj = weakObj;
if(strongObj){
// do something ...
}
});
这种方式先判断 Obj 是否被释放,如果未释放在执行我们的代码的时候保证其可用性。
__block
直接上代码:
MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object-1";
ZXLog(@"obj",obj);
__block MyObject *blockObj = obj;
obj = nil;
ZXLog(@"blockObj -1",blockObj);
void(^testBlock)(void) = ^(){
ZXLog(@"blockObj - block",blockObj);
MyObject *obj2 = [[MyObject alloc]init];
obj2.text = @"my-object-2";
ZXLog(@"obj2",obj2);
blockObj = obj2;
ZXLog(@"blockObj - block",blockObj);
};
ZXLog(@"%@",testBlock);
ZXLog(@"blockObj -2",blockObj);
testBlock();
ZXLog(@"blockObj -3",blockObj);
打印结果:
变量内存地址:0x7fff5ddc1c78, 变量值:0x60000000ac60, 指向对象值:<MyObject: 0x60000000ac60>, --> obj
变量内存地址:0x7fff5ddc1c70, 变量值:0x60000000ac60, 指向对象值:<MyObject: 0x60000000ac60>, --> blockObj -1
变量内存地址:0x7fff5ddc1c30, 变量值:0x60400045ce00, 指向对象值:<__NSMallocBlock__: 0x60400045ce00>, --> %@
变量内存地址:0x6040004588f8, 变量值:0x60000000ac60, 指向对象值:<MyObject: 0x60000000ac60>, --> blockObj -2
变量内存地址:0x6040004588f8, 变量值:0x60000000ac60, 指向对象值:<MyObject: 0x60000000ac60>, --> blockObj - block
变量内存地址:0x7fff5ddc1ba8, 变量值:0x60000000ace0, 指向对象值:<MyObject: 0x60000000ace0>, --> obj2
变量内存地址:0x6040004588f8, 变量值:0x60000000ace0, 指向对象值:<MyObject: 0x60000000ace0>, --> blockObj - block
变量内存地址:0x6040004588f8, 变量值:0x60000000ace0, 指向对象值:<MyObject: 0x60000000ace0>, --> blockObj -3
可以看到在 block 声明前后 blockObj 的内存地址是有所变化的,这涉及到 block 对外部变量的内存管理问题,大家可以看扩展阅读中的几篇文章,对此有较深入的分析。
下面来看看 __block 能不能避免循环引用的问题:
MyObject *obj = [[MyObject alloc]init];
obj.text = @"11111111111111";
ZXLog(@"obj",obj);
__block MyObject *blockObj = obj;
obj = nil;
void(^testBlock)(void) = ^(){
ZXLog(@"blockObj - block",blockObj);
};
obj = nil;
testBlock();
ZXLog(@"blockObj",blockObj);
打印结果:
变量内存地址:0x7fff57e48c78, 变量值:0x60000001b520, 指向对象值:<MyObject: 0x60000001b520>, --> obj
变量内存地址:0x604000457818, 变量值:0x60000001b520, 指向对象值:<MyObject: 0x60000001b520>, --> blockObj - block
变量内存地址:0x604000457818, 变量值:0x60000001b520, 指向对象值:<MyObject: 0x60000001b520>, --> blockObj
当外部 obj 指向 nil 的时候,obj 理应被释放,但实际上 blockObj 依然强引用着 obj,obj 其实并没有被真正释放。因此使用 __block 并不能避免循环引用的问题。
但是我们可以通过手动释放 blockObj 的方式来释放 obj,这就需要我们在 block 内部将要退出的时候手动释放掉 blockObj ,如下这种形式:
MyObject *obj = [[MyObject alloc]init];
obj.text = @"11111111111111";
ZXLog(@"obj",obj);
__block MyObject *blockObj = obj;
obj = nil;
void(^testBlock)(void) = ^(){
ZXLog(@"blockObj - block",blockObj);
blockObj = nil;
};
obj = nil;
testBlock();
ZXLog(@"blockObj",blockObj);
必须记住在 block 底部释放掉 block 变量,这其实跟 MRC 的形式有些类似了,不太适合 ARC这种形式既能保证在 block 内部能够访问到 obj,又可以避免循环引用的问题,但是这种方法也不是完美的,其存在下面几个问题
当在 block 外部修改了 blockObj 时,block 内部的值也会改变,反之在 block 内部修改 blockObj 在外部再使用时值也会改变。这就需要在写代码时注意这个特性可能会带来的一些隐患
__block 其实提升了变量的作用域,在 block 内外访问的都是同一个 blockObj 可能会造成一些隐患。
总结
__weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
__block 本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是 __block 修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。
但是__block有一点:这只是限制在ARC环境下。在非arc下,__block是可以避免引用循环的。