前言
前面我们了解了block的捕获变量机制和 block的copy实现,本文通过底层代码说一下block的循环引用
为什么 block 会产生循环引用
Q: 为什么 block 会产生循环引用?
相互循环引用:
如果当前block访问当前对象的某一成员变量,会讲当前对象进行捕获,可能会对它产生强引用。根据block的变量捕获机制,如果block被拷贝到堆上,且捕获的是对象类型的auto变量,则会连同其所有权修饰符一起捕获,所以如果对象是__strong修饰,则block会对它产生强引用(如果block在栈上就不会强引用)。而当前block可能又由于当前对象对其有一个强引用,就产生了相互循环引用的问题
多方循环引用:
如果使用__block的话,在ARC下可能会产生循环引用,由于__block修饰符会将变量包装成一个对象,如果block被拷贝到堆上,则会直接对__block变量产生强引用,而__block如果修饰的是对象的话,会根据对象的所有权修饰符做出相应的操作,形成强引用或者弱引用,如果对象是__strong修饰,则__block变量对它产生强引用,如果这时候该对象是对block持有强引用的话,就产生了三方循环引用的问题。
相互循环引用
多方循环引用
为什么我们要处理循环引用?
循环引用会导致实例对象不能释放 也就是实例对象所占用的内存不能及时被系统回收,会造成内存泄漏。
内存泄漏又是什么?危害是什么?
已经动态分配的堆内存由于某种原因程序未释放或无法释放造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
常见的内存泄漏有哪些
1、对象之间的循环引用问题
2、block的循环引用
3、delegate 的循环引用
4、通知的循环引用
5、NSTimer和target的循环引用
6、WKWebView 和self 之间的循环引用
了解更多看我这篇文章
所以我们在ARC环境下 特别容易block循环引用。
ARC
定义一个 Person类 代码如下
typedef void(^MyBlock)(void);
@interface Person : NSObject {
@public NSString *name;
}
@property (nonatomic, assign) int age;
@property (nonatomic, strong) MyBlock myblock;
@property (nonatomic, strong) void(^block2)(void);
@end
@implementation Person
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
VC类中调用
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
Person *person = [[Person alloc] init];
person.age = 30;
person.myblock = ^{
NSLog(@"-------30");
};
NSLog(@"%@",[person.myblock class]);
NSLog(@"%ld",CFGetRetainCount((CFTypeRef)(person)));
}
- (void)dealloc {
NSLog(@"%s",__func__);
}
输出
__NSGlobalBlock__
1
-[Person dealloc]
-[VC dealloc]
分析
Person实例对象销毁是正常
VC实例对象销毁是正常
看一个不正常的情况 代码如下
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
Person *person = [[Person alloc] init];
person.age = 30;
person.myblock = ^{
NSLog(@"-------%d", person.age);
};
NSLog(@"%ld",CFGetRetainCount((CFTypeRef)(person)));
}
- (void)dealloc {
NSLog(@"%s",__func__);
}
输出
__NSMallocBlock__
3
-[VC dealloc]
发生循环引用导致Person实例对象没有被释放,会引起内存泄漏
Clang
Person.cpp
重要代码注解
// 所有对象的结构体的类型都是objc_object
typedef struct objc_object Person;
//objc_object是只包含一个成员变量isa的结构体
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
struct NSObject_IMPL {
__unsafe_unretained Class isa;
};
// 对象的本质就是结构体 Person实例对象 被转化为Person_IMPL结构体
struct Person_IMPL {
// 对象的继承就是父类作为成员变量
// isa指向NSobject
struct NSObject_IMPL NSObject_IVARS;
// _name 没有声明成属性 没有帮我们生成 set get方法
NSString *__strong _name;
int _age;
__strong MyBlock _myblock;
void (*_block2)();
};
// @property (nonatomic, assign) int age;
// @property (nonatomic, strong) MyBlock myblock;
// @property (nonatomic, strong) void(^block2)(void);
/* @end */
// @implementation Person
// 根据属性修饰符 生成age属性get set方法
static int _I_Person_age(Person * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Person$_age)); }
static void _I_Person_setAge_(Person * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_Person$_age)) = age; }
// 生成 myblock属性 get set方法
static void(* _I_Person_myblock(Person * self, SEL _cmd) )(){ return (*(__strong MyBlock *)((char *)self + OBJC_IVAR_$_Person$_myblock)); }
static void _I_Person_setMyblock_(Person * self, SEL _cmd, MyBlock myblock) { (*(__strong MyBlock *)((char *)self + OBJC_IVAR_$_Person$_myblock)) = myblock; }
// 生成 block2属性 get set方法
static void(* _I_Person_block2(Person * self, SEL _cmd) )(){ return (*(void (**)())((char *)self + OBJC_IVAR_$_Person$_block2)); }
static void _I_Person_setBlock2_(Person * self, SEL _cmd, void (*block2)()) { (*(void (**)())((char *)self + OBJC_IVAR_$_Person$_block2)) = block2; }
static void _I_Person_dealloc(Person * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zm_558cwfjs099fbm2r8kxg8wt00000gt_T_VC_60be72_mi_0, __func__);
}
// @end
vc.cpp
// VC实例对象
struct VC_IMPL {
struct UIViewController_IMPL UIViewController_IVARS;
};
struct __VC__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __VC__viewDidLoad_block_desc_0* Desc;
// person指针变量 指向Person实例对象 __strong 表示强引用Person实例对象
Person *__strong person;
__VC__viewDidLoad_block_impl_0(void *fp, struct __VC__viewDidLoad_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
分析
-
oc对象编译成C++后的本质就是一个结构体Person_IMPL,该结构体的第一个成员变量就是isa指针,指向类对象本身,@property修饰的属性,系统会自动生成一个结构体成员变量,还为之生成getter和setter方法 这些可以看我之前的文章
-
person实例对象结构体Person_IMPL中含有_MyBlock变量,通过setter方法_I_Person_myblock将_MyBlock对象(等号右边)赋值给_MyBlock变量,因此_MyBlock指向_MyBlock对象(强引用)
-
在__VC__viewDidLoad_block_impl_0结构体中,我们看到Person *__strong person,所以,block对象本身对Person实例对象也是强引用
-
block对象结构体__main_block_impl_0通过其内部成员指针变量Person *__strong per持有Person实例对象(强引用),而Person实例对象结构体Person_IMPL通过其内部成员指针变量_MyBlock持有block对象(强引用)因此二者构成循环引用。
图示,我们仔细扣一下这里面的细节
对象持有block的过程
// 实现block
person.myblock = ^{
NSLog(@"30");
};
// 根据上面的实现 生成block结构体
__VC__viewDidLoad_block_impl_0 {
.....
}
// 判断结构体地址 是堆block还是栈block还是全局block
// 编译器转化为objc_msgSend 把判断的结果 也就是结构体地址扔给 setMyblock 方法
((void (*)(id, SEL, MyBlock))(void *)objc_msgSend)((id)person, sel_registerName("setMyblock:"), ((void (*)())&__VC__viewDidLoad_block_impl_0((void *)__VC__viewDidLoad_block_func_0, &__VC__viewDidLoad_block_desc_0_DATA)));
// 参数A 就是 结构体地址
参数A: (void (*)())&__VC__viewDidLoad_block_impl_0((void *)__VC__viewDidLoad_block_func_0, &__VC__viewDidLoad_block_desc_0_DATA))
// setMyblock 也就是Myblock的set方法 被上面的objc_msgSend 调用
[person setMyblock: 参数A];
// 将参数A传递进来 赋给OBJC_IVAR_$_Person$_myblock成员变量
static void _I_Person_setMyblock_(Person * self, SEL _cmd, MyBlock myblock) {
(*(__strong MyBlock *)((char *)self + OBJC_IVAR_$_Person$_myblock)) = myblock;
}
person对象通过block成员变量就持有block
block 持有person对象的过程
Person *person = [[Person alloc] init];
// 其实就是指针变量person 强引用[Person alloc]对象
Person *__strong person = [[Person alloc] init]; // 思考下这里 弄成 __weak 会怎样
person.age = 30;
person.myblock = ^{
NSLog(@"%@", person.age);
};
struct __VC__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __VC__viewDidLoad_block_desc_0* Desc;
// block捕获机制 根据变量强弱 内部保持一致
Person *__strong person;
__VC__viewDidLoad_block_impl_0(void *fp, struct __VC__viewDidLoad_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
解决思路
打破循环引用,只需要将其中一个强引用变成弱引用即可,那么要改变哪一个弱引用呢?
block从堆上被移除时候 会调用_Block_object_dispose 函数,该函数会自动释放引用的auto变量 也就是说
Person实例对象内部拥有block属性,当该实例对象销毁时,其block属性也会随之销毁,所以我们只需要将block对象中的Person类型指针变成弱指针即可
解决方法
ARC
使用__weak
使用__unsafe_unretained
使用__block解决(必须要调用block)
MRC
使用__unsafe_unretained
使用__block解决
ARC环境
例子1 __weak
使用 weak 修饰
Person *person = [[Person alloc] init];
person.age = 30;
__weak Person *weakPer = person;
person.myblock = ^{
NSLog(@"%d", weakPer.age);
};
NSLog(@"%@",[person.myblock class]);
输出
__NSMallocBlock__
-[Person dealloc]
-[VC dealloc]
clang
struct __VC__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __VC__viewDidLoad_block_desc_0* Desc;
Person *__weak weakPer;
__VC__viewDidLoad_block_impl_0(void *fp, struct __VC__viewDidLoad_block_desc_0 *desc, Person *__weak _weakPer, int flags=0) : weakPer(_weakPer) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
分析
block对象中,Person指针变量类型变成了__weak类型 Person 实例对象释放正常 VC释放正常
还有一种写法
Person *person = [[Person alloc] init];
person.age = 30;
//__weak Person *weakPer = person;
__weak typeof(person) weakPer = person;
person.myblock = ^{
NSLog(@"%d", weakPer.age);
};
person.myblock();
NSLog(@"%@",[person.myblock class]);
例子2 __unsafe_unretained
使用 __unsafe_unretained
Person *person = [[Person alloc] init];
person.age = 30;
__unsafe_unretained Person *weakPer = person;
person.myblock = ^{
NSLog(@"%d", weakPer.age);
};
person.myblock();
NSLog(@"%@",[person.myblock class]);
输出
30
__NSMallocBlock__
-[Person dealloc]
-[VC dealloc]
注:__weak修饰和__unsafe_unretained的区别 看我这篇文章
例子3 __block
使用 __block
__block Person *person = [[Person alloc] init];
person.age = 30;
person.myblock = ^{
NSLog(@"%d", person.age);
person = nil;
};
NSLog(@"%@",[person.myblock class]);
person.myblock();
输出
__NSMallocBlock__
30
-[Person dealloc]
-[VC dealloc]
clang
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__strong person;
};
struct __VC__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __VC__viewDidLoad_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref 这默认是强持有
__VC__viewDidLoad_block_impl_0(void *fp, struct __VC__viewDidLoad_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
分析
-
block对象结构体 __VC__viewDidLoad_block_impl_0
-
__block变量对象结构体 __Block_byref_person_0
-
block对象持有__block对象 __block对象又持有Person实例对象,Person实例对象又持有block对象 构成3角循环
- 通过block回调 ,Person实例对象指针被置为nil,而该指针本质是__block对象中的Person *__strong per指针,因此该指针不可能再指向Person实例对象了,所以,第2条持有就断开了,打破了三角循环
- 需要加 __block 需要调用block 还需要把指针制为nil 一旦忘记将指针置为nil,就会造成内存泄露,所以改方法比较麻烦 推荐使用 weak
MRC
该环境下不支持__weak修饰
例子4 __unsafe_unretained
使用 __unsafe_unretained
- (void)test {
Person *person = [[Person alloc] init];
person.age = 30;
NSLog(@"block前%ld",person.retainCount);
__unsafe_unretained Person *uup = person;
NSLog(@"__unsafe_unretainedh后%ld",person.retainCount);
person.myblock = ^{
NSLog(@"%d", uup.age);
};
NSLog(@"block后%ld",person.retainCount);
NSLog(@"%@",[person.myblock class]);
person.myblock();
[person release];
person = nil;
}
输出
block前1
__unsafe_unretainedh后1
block后1
__NSMallocBlock__
30
-[Person dealloc]
-[VC dealloc]
执行 copy
person.myblock = [^{
NSLog(@"%d", uup.age);
} copy];
输出
block前1
__unsafe_unretainedh后1
block后1
__NSMallocBlock__
30
-[Person dealloc]
-[VC dealloc]
分析
- 创建并持有1次 block copy到堆区 对象retainCount+1 变成2 对该对象发送release消息时,其retainCount自动减1,所以当程序结束时,Person实例对象retainCount为1,其内存并不会被系统回收从而导致内存泄露。
- 那么加上 __unsafe_unretained修饰后,无论后面有多少次retain或者copy操作,Person实例对象的retainCount始终为1 ,对该对象发送release消息,其retainCount值变为0,此时内存被回收,而不会导致内存泄露的问题
例子5 __block
__block修饰
- (void)test {
Person *person = [[Person alloc] init];
person.age = 30;
NSLog(@"block前%ld",person.retainCount);
__block Person *bp = person;
NSLog(@"__block后%ld",person.retainCount);
person.myblock = [^{
NSLog(@"%d", bp.age);
} copy];
NSLog(@"block后%ld",person.retainCount);
NSLog(@"%@",[person.myblock class]);
person.myblock();
[person release];
person = nil;
}
输出
block前1
__block后1
block后1
__NSMallocBlock__
30
-[Person dealloc]
分析
MRC下,__block修饰对象类型的auto对象类型,系统生成的__block对象并不会根据test()方法中的person指针是强指针类型而对Person实例对象[[Person alloc进行retain操作
所以此时,__block的作用相当于__unsafe_unretained的作用,原理一样
ARC
例子6 __strong
ARC环境下,弱指针不能通过"->"形式来访问对象的成员变量
weakPer很可能为为空(即有可能提前被释放了),所以必须使用强指针来访问
- (void)test {
Person *person = [[Person alloc] init];
person.age = 30;
person->_name = @"yang";
__weak typeof(person) weakPer = person;
person.myblock = ^{
// 其实就是骗编译器 block 内部引用的还是 weakPer
__strong typeof(weakPer) strongPer = weakPer;
NSLog(@"%d", weakPer.age);
NSLog(@"%@", strongPer->_name);
};
NSLog(@"%@",[person.myblock class]);
person.myblock();
}
输出
__NSMallocBlock__
30
yang
-[Person dealloc]
-[VC dealloc]
clang
struct __VC__test_block_impl_0 {
struct __block_impl impl;
struct __VC__test_block_desc_0* Desc;
Person *__weak weakPer;
__VC__test_block_impl_0(void *fp, struct __VC__test_block_desc_0 *desc, Person *__weak _weakPer, int flags=0) : weakPer(_weakPer) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
block 内部成员变量weakPer还是弱引用Person实例对象 是weak类型,并不受block代码块内部的__strong转化,该转化只是为了骗取编译器通过编译而已
例子7 没有循环引用
@interface VC ()
@property (nonatomic, copy) NSString *name;
@end
@implementation VC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.name = @"yang";
void(^block)(void) = ^{
NSLog(@"%@",self.name);
};
NSLog(@"block类型%@",[block class]);
block();
}
- (void)dealloc {
NSLog(@"vc dealloc");
}
输出
block类型__NSMallocBlock__
yang
vc dealloc
clang
// vc 不持有 block
struct VC_IMPL {
struct UIViewController_IMPL UIViewController_IVARS;
NSString *_name;
};
static void _I_VC_viewDidLoad(VC * __strong self, SEL _cmd) {
void(*block)(void) = ((void (*)())&__VC__viewDidLoad_block_impl_0((void *)__VC__viewDidLoad_block_func_0, &__VC__viewDidLoad_block_desc_0_DATA, self, 570425344));
}
struct __VC__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __VC__viewDidLoad_block_desc_0* Desc;
VC *const __strong self;
__VC__viewDidLoad_block_impl_0(void *fp, struct __VC__viewDidLoad_block_desc_0 *desc, VC *const __strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
分析
block持有self 强引用self
self不持有block 不构成循环引用
总结
-
block对象与OC对象相互持有(强引用) 才会造成相互循环引用
-
block对象持有__block变量对象 __block变量对象持有oc对象 oc对象持有block对象 构成3角循环引用
-
循环引用会导致实例对象不能释放 也就是实例对象所占用的内存不能及时被系统回收,会造成内存泄漏。