block(6) - ARC、MRC及block之间的循环引用

2、易发生在block

如:

//.h文件在ARC
typedef void(^block)(void);

@interface Test : NSObject

@property (nonatomic,copy)block testBlock;
@property (nonatomic,assign)NSInteger temp;
@property (nonatomic,copy)NSString* str;

- (void)inmethod1;

@end
//mian文件在MRC,为了能手动release观察是否发生循环引用。
int main(int argc, char * argv[]) {

    Test *test = [[Test alloc]init];

    [test inmethod1];

    [test release];

    NSLog(@"程序即将结束");
}

1、在ARC中(.m文件在ARC中来即测试在ARC中block的影响而导致的循环引用)

这里写图片描述

分析 :可以看到没有调用dealloc,说明出现了循环引用,那么循环引用怎么产生的呢。

  • self 持有 _textblock , _textblock因为通过点方法(setter方法)把inmethod1中的stackblock作copy到了堆上变成了mallocblock,所以_textblock持有mallocblock(修饰符为copy),而在mallocblock中使用了self有关的系列(堆会对self持有)。所以发生了循环引用。

又如果是这样:
这里写图片描述

分析 :可以看到没有调用dealloc,说明出现了循环引用,那么循环引用怎么产生的呢。

  • 可以看到这里直接对testBlock进行操作,首先因为self持有_testBlock,而后的block进行了 = 操作,会导致调用objc_retainBlock->Block_copy->_Block_copy_internal方法链。并导致__NSStackBlock类型的block转换为NSMallocBlock类型。因为testBlock是strong的,所以在_NSMallocBlock中而在mallocblock中使用了self有关的系列(堆会对self持有)。所以发生了循环引用。

2、在MRC中(.m文件在MRC中来即测试在MRC中block的影响而导致的循环引用)

这里写图片描述

分析 :可以看到没有调用dealloc,说明出现了循环引用,那么循环引用怎么产生的呢。

  • self 持有 _textblock , _textblock因为通过点方法(setter方法)把inmethod1中的stackblock作copy到了堆上变成了mallocblock,所以_textblock持有mallocblock(修饰符为copy),而在mallocblock中使用了self有关的系列(堆会对self持有)。所以发生了循环引用。

如果是另一种情况:
这里写图片描述

分析 :可以看到调用dealloc了,说明没有出现了循环引用,那是为什么呢。

  • 可以看到这里直接对_testBlock进行操作,虽然self持有_testBlock,但是inmethod1中的block始终是一个stackblock,虽然在stackblock中使用了self的相关系列(但是栈不会对self持有),所以就不会出现循环引用了。

查看cpp代码

    typedef void(*block)(void);

    struct Test_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    block _testBlock;         //生成__testBlock实例变量
    NSInteger _temp;          //生成_temp实例变量
    NSString *_str;           //生成实例变量
};

重要部分代码:

struct __Test__inmethod1_block_impl_0 {

  ......

  Test *self;   //注意捕获进来了

  __Test__inmethod1_block_impl_0(void *fp, struct __Test__inmethod1_block_desc_0 *desc, Test *_self, int flags=0) : self(_self) {
  ......
  }
};
//代码块初始化时候需要填充的结构体struct __Test__inmethod1_block_impl_0
    struct __Test__inmethod1_block_impl_0 {

      ......

      Test *self;           

      __Test__inmethod1_block_impl_0(void *fp, struct __Test__inmethod1_block_desc_0 *desc, int flags=0) {
      ......
      }
    };

解决此类循环引用的方法:
方法 1:

ARC中

    - (void)inmethod1
    {
        __weak typeof(self) weakSelf = self;    //可以看到这里多了这句
        //用__weak修饰归零弱引用self,使得后面即使成环了,但是有一个弱引用,使得循环引用无法发生
        //或者用__unsafe_unretain

        self.testBlock = ^(void)    //调用setter方法对实例变量进行操作
        {
            _temp = 1;        //这里直接访问self中的实例变量,可能会有循环引用的
            _str = @"one";     //这里直接访问self中的实例变量,可能会有循环引用的

            weakSelf.temp = 1;
            //这里没有会发生循环引用的警告了
            weakSelf.str = @"one";
            //这里没有会发生循环引用的警告了

            NSLog(@"%ld",_temp);还是生成了Test *self
           //Capturing 'self' strongly in this block is likely to lead to a retain cycle
            NSLog(@"%@",_str);还是生成了Test *self
           //Capturing 'self' strongly in this block is likely to lead to a retain cycle

           //疑问 2:why?因为弱引用weakself没有被调用,而是依然调用的强引用self

            NSLog(@"%ld",weakSelf.temp);
            //这里没有会发生循环引用的警告了
            NSLog(@"%@",weakSelf.str);
            //这里没有会发生循环引用的警告了
        };

        _testBlock();
    }

查看转换成的cpp代码

    //inmethod1
    struct __Test__inmethod1_block_impl_0 {
     ......
      typeof (self) weakSelf;    //可以看到在block里是相当使用了弱引用的

      __Test__inmethod1_block_impl_0(void *fp, struct __Test__inmethod1_block_desc_0 *desc, typeof (self) _weakSelf, int flags=0) : weakSelf(_weakSelf) {
        ......
      }
    };

    //block的内部实现函数
    static void __Test__inmethod1_block_func_0(struct __Test__inmethod1_block_impl_0 *__cself) {
          //这里和上面一样相当使用了弱引用
          typeof (self) weakSelf = __cself->weakSelf; // bound by copy
          ......
    }

    //inmthod内部实现
    static void _I_Test_inmethod1(Test * self, SEL _cmd) {
    __attribute__((objc_ownership(none))) typeof(self) weakSelf = self;
    ......
}

结论:可以看到即使使用__weak或者__unsafe_unretained将self弱引用后,如果在block内部调用的不是self的弱引用,或者_temp、_str这样调用依然会引起循环引用的。


方法 2:方法 1是有缺陷的,所以在方法 1 的基础上加以完善。

//ARC
- (void)inmethod1
    {
        __weak typeof(self) weakSelf = self;    //可以看到这里多了这句
        //用__weak修饰归零弱引用self,使得后面即使成环了,但是有一个弱引用,使得循环引用无法发生
        //或者用__unsafe_unretain

        self.testBlock = ^(void)    //调用setter方法对实例变量进行操作
        {
            _temp = 1;    //这里直接访问self中的实例变量,可能会有循环引用的
            _str = @"one";     //这里直接访问self中的实例变量,可能会有循环引用的

           /* --------------------------------------------- */
           __strong typeof(self) strongSelf = weakSelf;
           //这里多了这一句,那么疑问 1,这里这个strongSelf有什么作用呢?
           /* --------------------------------------------- */

            strongSelf.temp = 1;
            //这里没有会发生循环引用的警告了
            strongSelf.str = @"one";
            //这里没有会发生循环引用的警告了

            //NSLog(@"%ld",_temp);还是生成了Test *self
           Capturing 'self' strongly in this block is likely to lead to a retain cycle
            //NSLog(@"%@",_str);还是生成了Test *self
           Capturing 'self' strongly in this block is likely to lead to a retain cycle

            NSLog(@"%ld",strongSelf.temp);
            //这里没有会发生循环引用的警告了
            NSLog(@"%@",strongSelf.str);
            //这里没有会发生循环引用的警告了
        };

        _testBlock();
    }

方法 3:比方法 2更好

//ARC
- (void)inmethod1
    {
        @weakify(self)     //和方法2那的作用类似

        self.testBlock = ^(void)    //调用setter方法对实例变量进行操作
        {
            _temp = 1;    //这里直接访问self中的实例变量,可能会有循环引用的
            _str = @"one";     //这里直接访问self中的实例变量,可能会有循环引用的

           @strongify(self)    //多了这一句, //和方法2那的作用类似

            strongSelf.temp = 1;
            //这里没有会发生循环引用的警告了
            strongSelf.str = @"one";
            //这里没有会发生循环引用的警告了

            //NSLog(@"%ld",_temp);还是生成了Test *self
           Capturing 'self' strongly in this block is likely to lead to a retain cycle
            //NSLog(@"%@",_str);还是生成了Test *self
           Capturing 'self' strongly in this block is likely to lead to a retain cycle

            NSLog(@"%ld",strongSelf.temp);
            //这里没有会发生循环引用的警告了
            NSLog(@"%@",strongSelf.str);
            //这里没有会发生循环引用的警告了
        };

        _testBlock();
    }

猜你喜欢

转载自blog.csdn.net/qq_28446287/article/details/79528305