美文网首页工作生活
关于block中__strong与__weak的一点思考

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

作者: 章鱼paul帝 | 来源:发表于2019-06-30 20:44 被阅读0次

    值传递&&引用传递

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

    相关文章

      网友评论

        本文标题:关于block中__strong与__weak的一点思考

        本文链接:https://www.haomeiwen.com/subject/tyficctx.html