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

2019-04-03  本文已影响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都会造成内存泄漏,深入理解就能比较去理解循环引用是怎么回事。

上一篇下一篇

猜你喜欢

热点阅读