本文主要分享一下对block内存管理和循环引用的理解,如果对block的本质和类型还不太理解的,建议先阅读下前面两篇文章,打个底,再继续阅读本文的内容。
内存管理
要写内存管理这块的内容,我们需要一个引子,前面的文章中可以看到过,但是没有拿出来说的一个地方,那就是__block修饰的对象类型。之前文章中大部分都是用了__block修饰的基础数据类型的变量,而没有着重说过对象类型。
__block修饰的对象类型
定义一个person类,再添加一个age属性,当person的实例被block捕获之后,我们通过clang编译之后看一下C++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *p;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_p, int flags=0) : p(_p) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *p = __cself->p; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_57603e_mi_0,(long)((NSInteger (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->p, (void*)src->p,3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
这里我们主要是想关注一下__main_block_desc_0,看过前面文字的小伙伴一定知道,如果是捕获的外部变量是一个基础数据类型,那么__main_block_desc_0中只有reserved和Block_size,而当捕获了一个对象类型的变量之后,这里结构体里面就增加了copy和dispose,用来做内存管理。
copy的实现分别就是__main_block_copy_0, 内部调用了_Block_object_assign函数,_Block_object_assign会对变量行成强引用。
dispose的实现内部调用了_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的函数。
上面说的是没有被__block修饰的对象,下面再对比一下被修饰过之后捕获到block内部有什么不同。
struct __Block_byref_p_0 {
void *__isa;
__Block_byref_p_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *p;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_p_0 *p; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_p_0 *_p, int flags=0) : p(_p->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
两点不同:
- 之前分析过,被
__block修饰之后,会生成一个对应的结构体__Block_byref_p_0 __Block_byref_p_0也新增了copy和dispose
所以这里总结一下被block捕获的变量的内存管理:
- 当block在栈上时,对捕获的变量不会产生强引用
- 当block被copy到堆上时 ,都会通过copy函数来处理变量
- 当block从堆上移除时,都会通过dispose函数来释放他们
再看一下__block修饰的对象,copy和dispose函数的实现
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->p, 8/*BLOCK_FIELD_IS_BYREF*/);}
最后一个参数8/*BLOCK_FIELD_IS_BYREF*和上面的3/*BLOCK_FIELD_IS_OBJECT*,可以理解为枚举,用来区分是__block变量还是对象。
循环引用
继续再讨论一下循环引用的问题,这个也是block部分面试常被问到的问题, 举一个简单的循环引用的例子:

至于循环引用是如何引起的,这个其实并不是block相关的知识点,简单来说就是相互持有导致不能释放。只是block在使用中必须要注意的一个地方,后期如果有空写一写关于内存管理方法的文章,再详细说一说这个循环引用,本文主要分享几个解决block循环引用的方法。
先说一下使用了__weak之后,用clang命令 编译的问题

解决循环引用
- __weak 、__unsafe_unretained
上面示例代码中的循环引用,就可以用
__weak解决
- (void)test_block {
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"this is a block - %ld",(long)weakSelf.age);
};
}
但是这样解决不算完美,还是有bug,比如block中有延迟调用,用__weak修饰后,这里类已经销毁,但是block中延迟调用的代码还未执行。
- (void)test_block {
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"this is a block - %ld",(long)weakSelf.age);
});
};
self.block();
}
关于__weak 和 __unsafe_unretained的区别呢,主要是安全性的区别,__weak 指向的对象销毁时,会自动让指针置为nil,而__unsafe_unretained没这个功能,是不安全的。
- __block
__block解决循环引用有两点要注意,第一block必须要调用,第二block中需要把self置为nil。
- (void)test_block {
NSLog(@"%s",__func__);
__block Person *blockSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"this is a block - %ld",(long)blockSelf.age);
blockSelf = nil;
});
};
self.block();
}
- MRC情况下的循环引用如何解决

对于以上几个解决block循环引用的方法,各有优缺,开发中我们还是要根据实际情况,去选择某一种解决的方案。