关于block的循环引用,在网上已经有非常多的文章,但是大多比较浅显的一些介绍,也就是互相持有对象引起的循环引用。看了很多篇文章都觉得非常的抽象,那么让我们从底层看看,具体的原因是因为什么。
我们来分析下几个情景解析循环引用是怎样互相持有对象:
情景一:
- 创建一个类Age继承NSObject
@interface Age : NSObject
- (void)test;
@property (copy, nonatomic)void (^vblock) (void);
@end
// 在.m文件中实现block方法
@implementation Age
- (void)test {
self.block2 = ^{
};
}
- 在其他类实例化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指针
- 接下来我们将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。
因此形成了一个闭环的回路,才会造成循环引用。
情景二:
- 同样的,在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没有关系,因此不会形成循环引用的结果。
- 这一次,我们再变一下参数v2的性质。将他作为Person的成员属性
@interface Person ()
@property (copy, nonatomic)Age *v2;
@end
@implementation Person
- (void)blockFun2 {
self.v2 = [[Age alloc]init];
self.v2.vblock = ^{
NSLog(@"%@",self);
};
}
-
看看底层的代码
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都会造成内存泄漏,深入理解就能比较去理解循环引用是怎么回事。
网友评论