工作生活

关于block中__strong与__weak的一点思考

2019-06-30  本文已影响0人  章鱼paul帝

值传递&&引用传递

首先从函数谈起,函数参数传递的类型分为值传递和引用传递两种,
值传递的过程指的是在实参给形参赋值的过程中,函数会为形参开辟一块新的内存用于存储实参的值,而对于引用传递来说,函数不会为形参开辟一块新的内存,形参指向实参的内存,下面通过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的回调能够正常执行。

上一篇下一篇

猜你喜欢

热点阅读