美文网首页
深入浅出Block循环引用的底层解析

深入浅出Block循环引用的底层解析

作者: 凉秋落尘 | 来源:发表于2019-04-03 14:29 被阅读0次

    关于block的循环引用,在网上已经有非常多的文章,但是大多比较浅显的一些介绍,也就是互相持有对象引起的循环引用。看了很多篇文章都觉得非常的抽象,那么让我们从底层看看,具体的原因是因为什么。

    我们来分析下几个情景解析循环引用是怎样互相持有对象:

    情景一:

    1. 创建一个类Age继承NSObject
    @interface Age : NSObject
    - (void)test;
    @property (copy, nonatomic)void (^vblock) (void);
    @end
    
    // 在.m文件中实现block方法
    @implementation Age
    - (void)test {
        self.block2 = ^{
        };
    }
    
    1. 在其他类实例化Age并实现test方法
        Age *v2 = [[Age alloc]init];
        [v2 test];
    

    我们来看看runtime底层Person.m文件,是什么东西

    struct __Age__test_block_impl_0 {
      struct __block_impl impl;
      struct __Age__test_block_desc_0* Desc;
      Age *self;
      __Age__test_block_impl_0(void *fp, struct __Age__test_block_desc_0 *desc, Age *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __Age__test_block_func_0(struct __Age__test_block_impl_0 *__cself) {
      Age *self = __cself->self; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_l0_1pb20kzj3pgfnwybw2l9f_2h0000gn_T_Age_5edd37_mi_0,self);
        }
    static void __Age__test_block_copy_0(struct __Age__test_block_impl_0*dst, struct __Age__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __Age__test_block_dispose_0(struct __Age__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __Age__test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __Age__test_block_impl_0*, struct __Age__test_block_impl_0*);
      void (*dispose)(struct __Age__test_block_impl_0*);
    } __Age__test_block_desc_0_DATA = { 0, sizeof(struct __Age__test_block_impl_0), __Age__test_block_copy_0, __Age__test_block_dispose_0};
    
    static void _I_Age_test(Age * self, SEL _cmd) {
        ((void (*)(id, SEL, vblock2 _Nonnull))(void *)objc_msgSend)((id)self, sel_registerName("setBlock2:"), ((void (*)())&__Age__test_block_impl_0((void *)__Age__test_block_func_0, &__Age__test_block_desc_0_DATA, self, 570425344)));
    }
    

    _I_Age_test方法就是我们的test方法,先看_I_Age_test就可以了,前面的代码先不用观看。可以看出block其实是一个指向结构体__Age__test_block_impl_0的指针。指针的参数__Age__test_block_func_0是实现回调的方法。

    该回路为:self->注册block2方法->指向__Age__test_block_impl_0指针
    1. 接下来我们将block回调中传入self方法
    @implementation Age
    - (void)test {
        self.block2 = ^{
             NSLog(@"%@",self);
        };
    }
    

    再来看看底层

    struct __Age__test_block_impl_0 {
      struct __block_impl impl;
      struct __Age__test_block_desc_0* Desc;
      Age *self;
      __Age__test_block_impl_0(void *fp, struct __Age__test_block_desc_0 *desc, Age *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __Age__test_block_func_0(struct __Age__test_block_impl_0 *__cself) {
      Age *self = __cself->self; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_l0_1pb20kzj3pgfnwybw2l9f_2h0000gn_T_Age_e18fe9_mi_0,self);
        }
    static void __Age__test_block_copy_0(struct __Age__test_block_impl_0*dst, struct __Age__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __Age__test_block_dispose_0(struct __Age__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __Age__test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __Age__test_block_impl_0*, struct __Age__test_block_impl_0*);
      void (*dispose)(struct __Age__test_block_impl_0*);
    } __Age__test_block_desc_0_DATA = { 0, sizeof(struct __Age__test_block_impl_0), __Age__test_block_copy_0, __Age__test_block_dispose_0};
    
    static void _I_Age_test(Age * self, SEL _cmd) {
        ((void (*)(id, SEL, vblock2 _Nonnull))(void *)objc_msgSend)((id)self, sel_registerName("setBlock2:"), ((void (*)())&__Age__test_block_impl_0((void *)__Age__test_block_func_0, &__Age__test_block_desc_0_DATA, self, 570425344)));
    }
    

    对比一下就会发现__Age__test_block_impl_0的结构体多了一个Age *self成员。如下图:

    6B725DB2-3F17-499F-A8C8-734E24477394.png
    该回路为:self->注册block2方法->指向__Age__test_block_impl_0指针->指向成员变量self。

    因此形成了一个闭环的回路,才会造成循环引用。

    情景二:

    1. 同样的,在Age类定义block参数:
    @interface Age : NSObject
    @property (copy, nonatomic)void (^vblock) (void);
    @end
    

    然后定义Person类实现Age的block方法,并传入self

    - (void)blockFun2 {
        Age *v2 = [[Age alloc]init];
        v2.vblock = ^{
            NSLog(@"%@",self);
        };
    }
    
    image.png
    该回路为:v2->注册block2方法->指向__Age__test_block_impl_0指针->指向成员变量self。

    因为v2是一个创建的变量,跟self没有关系,因此不会形成循环引用的结果。

    1. 这一次,我们再变一下参数v2的性质。将他作为Person的成员属性
    @interface Person ()
    @property (copy, nonatomic)Age *v2;
    @end
    @implementation Person
    
    - (void)blockFun2 {
        self.v2 = [[Age alloc]init];
        self.v2.vblock = ^{
            NSLog(@"%@",self);
        };
    }
    
    1. 看看底层的代码


      image.png
    该回路为:self->注册v2->注册block2方法->指向__Age__test_block_impl_0指针->指向成员变量self。

    形成了闭合的环路,造成循环引用。

    情景三:

    创建一个viewController 和一个view

    // controller
    - (void)viewDidLoad {
        [super viewDidLoad];
        View2 *v1 = [[View2 alloc]init];
        v1.block3 = ^{
            self.name = @"";
        };
      //  [self.view addSubview:v1];
    }
    

    当没有调用addSubview的时候,该block3也是不存在循环引用的现象。

    该回路为:v1->注册block2方法->指向__Age__test_block_impl_0指针->指向成员变量self。

    但是当调用addSubview,v1已经指向了self.view形成环路。

    该回路为:self->注册view->v1->注册block2方法->指向__Age__test_block_impl_0指针->指向成员变量self。

    看到这里相信,不懂的人,应该就知道了block是因为什么造成了循环引用的问题。block的底层是创建了一个结构体的,将block作为指向结构体的指针。当block回调方法中,加入变量参数,结构体也会创建一个相应的结构体成员变量,将结构体指向该成员。因此不是所有的block都会造成内存泄漏,深入理解就能比较去理解循环引用是怎么回事。

    相关文章

      网友评论

          本文标题:深入浅出Block循环引用的底层解析

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