美文网首页
Block循环引用

Block循环引用

作者: 小心韩国人 | 来源:发表于2019-01-22 15:27 被阅读8次

    一说起Block,肯定就要说它的循环引用.这是个老生常谈的问题,也是我们面试中经常被问到的.循环引用就说:我中有你,你中有我.如图所示:


    循环引用关系

    今天我们将会通过底层代码,更加深刻的了解循环引用.

    typedef void (^HHBlock) (void);
    
    @interface Person : NSObject
    @property (nonatomic,copy)HHBlock block;
    @property (nonatomic,assign)int age;
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            Person *person = [[Person alloc]init];
            person.age = 20;
            person.block = ^{
                NSLog(@"age is %d",person.age);
            };
            NSLog(@"-----------------");
        }
        return 0;
    }
    

    我们在Person类中添加HHBlock , age两个属性,然后再 main 函数中访问 age 属性会怎么样呢?很显然这样会发生循环引用,我们在开发中也经常遇到这样的问题.他们之间的引用关系如下图:


    查看底层代码发现block内部的确对 person 进行了强引用:
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      Person *__strong person; // 对 person 产生了强引用
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    那我们怎么解决循环引用呢?我们可以试着分析一下,如下图所示,Block 内部和 Person 产生了互相强引用,那我们只要把其中的一条线变成弱引用就可以了.


    如果把第二条线变成弱引用,那么就会存在 person 对象还没销毁,block 就提前释放的情况,可能会影响 block 的正常使用,所以我们还是要 对 _block 成员变量保持强引用.所以最佳的解决办法就是把第一条线变成弱引用.
    • 在ARC环境下,有两种方法可以解决这种循环引用的问题:
      1: __weak
      2:__unsafe_unretained

    我们使用 __weak 然后看一下底层代码:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            Person *person = [[Person alloc]init];
            __weak Person *weakPerson = person;
            person.age = 20;
            person.block = ^{
                NSLog(@"my age is %d",weakPerson.age);
            };
            NSLog(@"-----------------");
            person.block();
        }
        return 0;
    }
    =====================================================
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      Person *__weak weakPerson; // block 内部对person的引用的确变成了弱引用
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    __weak转换成__unsafe_unretained再看看:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      Person *__unsafe_unretained weakPerson; // 底层已经转换为 __unsafe_unretained
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__unsafe_unretained _weakPerson, int flags=0) : weakPerson(_weakPerson) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    发现底层已经变成__unsafe_unretained,好尴尬,看不出来效果,我们运行一下看看效果:


    既然__weak 和 __unsafe_unretained都是弱引用,那么他们之间有什么区别呢?
    从名字我们可以看出来__unsafe_retained是不安全的,所谓的不安全就是:如果 Person 对象释放了,使用在 block 内部会把使用__weak修饰的 person 指针设置为 nil,而不会把使用__unsafe_retained修饰的指针设置为nil,所以会出现野指针的情况.
    • 使用 __block也可以解决循环引用的情况.
      使用__block解决循环引用
      使用__block修饰person后会出现3个对象Person对象,Block对象(__main_block_impl_0),__block对象(__Block_byref_weakPerson_0),他们之间的关系如下:
      __block对象的循环引用关系
      如上图所示,他们三者之间互相强引用,已经形成了一个⭕️,如果要使用__block就要及时切断切断一条线:
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            Person *person = [[Person alloc]init];
            __block Person *weakPerson = person;
            person.age = 20;
            person.block = ^{
                NSLog(@"my age is %d",weakPerson.age);
    //在 block 内部,把 person 对象置为nil
                weakPerson = nil;
            };
            NSLog(@"-----------------");
            person.block();
        }
        return 0;
    }
    

    运行以上代码发现 person 对象成功释放了,说明他们三者之间的循环引用被打断了.我们思考一下,在 block 内部把 person 置为nil,切断的是哪根线呢?

    总结:

    有三种方法可以解决 Block 的循环引用问题
    1: __weak
    2: __unsafe_unretained
    3: __block
    一般我们都使用__weak,因为__unsafe_unretained是不安全的,使用__block的前提条件是 Block 必须要调用,并且要在 Block内部把对象置为 nil.

    以上讲的都是 ARC环境下,如果在 MRC环境下怎么解决 Block 循环引用问题呢?

    因为在 MRC 环境下没有__weak关键字,所以在 MRC 下有两种解决方式:
    1: __unsafe_unretained
    2: __block
    需要注意的是,在 MRC 下, __blcok 对象内部是不会强引用外部对象的,所以在 MRC 下不需要把对象置为nil,也不是必须调用 blcok.

    相关文章

      网友评论

          本文标题:Block循环引用

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