关于block中__strong与__weak的一点思考
值传递&&引用传递
首先从函数谈起,函数参数传递的类型分为值传递和引用传递两种,
值传递的过程指的是在实参给形参赋值的过程中,函数会为形参开辟一块新的内存用于存储实参的值,而对于引用传递来说,函数不会为形参开辟一块新的内存,形参指向实参的内存,下面通过C++的例子来说明。
1 值传递
我们新建了一个名为Person
的类 实现了一个testWithNumber
的函数,在函数内打印了形参的地址
void Person::testWithNumber(int number){
cout << "函数内" << &number << endl;
}
函数外的调用代码如下
int realNumber = 10;
cout << "函数外" << &realNumber << endl;
p.testWithNumber(number);
此时实参 realNumber 的值10传递给形参number,函数会为形参开辟一块新的内存用于存储实参的值10,此时可以参考控制台的打印
函数外0x7ffeefbff510
函数内0x7ffeefbff4d4
此时如果修改形参number的值 并不会影响realNumber的值
2 引用传递
我们对上面的函数加以改造,C++中使用 &argument 来表示参数的引用传递
void Person::testWithNumber(int &number){
cout << "函数内" << &number << endl;
}
函数外的调用代码不变 此时控制台打印如下
函数外0x7ffeefbff510
函数内0x7ffeefbff510
可以看出,对于引用传递来说,函数不会为形参开辟一块新的内存,形参指向实参的内存,如果在函数内修改形参number的值,会影响到实参realNumber的值
Block中的__strong与__weak
block可以截获自动变量的值,所以block类似于上面所说的值传递
的过程。
所以下面代码显然会导致循环引用
NSLog(@"block之前 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
self.block = ^{
NSLog(@"%@",self);
};
NSLog(@"block之后 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
self持有block,因为在block中使用了self,block会实例化一个__strong修饰的指针指向self 从而导致循环引用,打印引用计数如下(尚不清楚为什么增加了2)
2019-06-22 13:44:41.451490+0800 newtest[21910:3687326] block之前 7
2019-06-22 13:44:41.451541+0800 newtest[21910:3687326] block之后 9
为了解决循环引用问题,经典的做法如下
NSLog(@"block之前 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
__weak typeof(self) wself = self;
self.block = ^{
NSLog(@"%@",wself);
};
NSLog(@"block之后 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
self持有block,因为在block中使用了wself,block会实例化一个__weak修饰的指针指向self,因为weak指针并不会增加self的引用计数,从而避免了循环引用的问题。打印引用计数如下
2019-06-22 13:55:43.660759+0800 newtest[21922:3689103] block之前 7
2019-06-22 13:55:43.660810+0800 newtest[21922:3689103] block之后 7
虽然循环引用就打破了,但是新的问题又来了。那就是会有self提前于block执行之前释放的场景,testBlock实体释放了,self就指向nil了,wself也会被置为nil,等block回来时,其实在向一个nil发消息。
这时候就到了__strong登场了,先通过一个例子看一下__strong是如何发挥作用的
__strong YZView *view1 = [[YZView alloc] init];
NSLog(@"retain count is %ld",CFGetRetainCount((__bridge CFTypeRef) view1));
__weak YZView *view2 = view1;
NSLog(@"retain count is %ld",CFGetRetainCount((__bridge CFTypeRef) view1));
__strong YZView *view3 = view2;
NSLog(@"retain count is %ld",CFGetRetainCount((__bridge CFTypeRef) view1));
打印结果如下
2019-06-22 14:49:16.097877+0800 newtest[22002:3697668] retain count is 1
2019-06-22 14:49:16.097895+0800 newtest[22002:3697668] retain count is 1
2019-06-22 14:49:16.097908+0800 newtest[22002:3697668] retain count is 2
所以我们把__strong应用到blcok来保证self不会被提前释放
NSLog(@"block之前 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
__weak typeof(self) wself = self;
self.block = ^{
__strong typeof(wself) sself = wself;
NSLog(@"%@",sself);
};
NSLog(@"block之后 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
但这其实是一种错误的使用方法,并没有增加self的引用计数,所以无法阻止self提前于block释放,引用计数打印如下
2019-06-22 14:56:58.031208+0800 newtest[22005:3698610] block之前 7
2019-06-22 14:56:58.031262+0800 newtest[22005:3698610] block之后 7
原因在于sself并非blcok捕获的外部变量,而是在block中内部生成的局部变量,所以sself只有在block实际调用的时候才会被赋予wself的值(此时wself可能是nil),并且增加self的引用计数,而在block调用结束后释放sself,并且减少self的引用计数,这个临时产生的“循环引用”就会被自动打破。由此可见,同步返回的block的__strong虽然不会导致循环引用,但是并不会起作用。
所以__strong只会对异步返回的block起作用
__weak typeof(self) wself = self;
self.block = ^{
__strong typeof(wself) sself = wself;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(3);
[sself test];
})
};
3秒后的打印结果如下
2019-06-22 14:56:58.031208+0800 newtest[22005:3698610] invoke test
2019-06-22 14:56:58.031262+0800 newtest[22005:3698610] self dealloc
具体的调用流程与打印如下
NSLog(@"block调用之前 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
self.block();
NSLog(@"block调用之后 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
2019-06-22 15:17:44.252439+0800 newtest[22012:3701260] block调用之前 7
2019-06-22 15:17:44.252520+0800 newtest[22012:3701260] block调用之后 8
异步调用的block回调会因为GCD引用sself的值从而强引用了self,使得GCD的回调能够正常执行。