一、循环引用的产生
1.1、代码示例
1、Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic,assign) int age; @property (nonatomic,copy) void(^block)(void); @end
2、Person.m
#import "Person.h" @implementation Person -(void)dealloc{ NSLog(@"---------Person__dealloc"); } @end
3、main.m
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *person = [[Person alloc] init]; person.age = 10; person.block = ^{ NSLog(@"-------:%d",person.age); }; NSLog(@"-------::::::::---------"); } return 0; }
4、编译后,打印日志:
block-01[14499:3644589] -------::::::::--------- Program ended with exit code: 0
发现Person对象没有被销毁。
1.2、流程分析
编译完代码之后,发现 Person 对象没有被销毁,让我们来分析一下原因,分为两段:
1、当程序编译完成时候:
1、main.m类中有一个person指针指向了Person类
2、底层新建一个block结构体对象,内部也有一个person指针,也指向了Person类(强引用)
3、Person类中有一个成员变量_block(属性block编译之后生成),它指向了block结构。
其结构如下图:
[外链图片转存失败,源站可能有防盗在这里插入!链机制,建描述]议将图片上https://传(imblog.snimg.Pl5Gcn/5a3fc0b04709486b6807d6aaf52c5327.png#pic_center)https://img-blog.csdnimg.c/5a3fc0b0470946b88607d6aaf52c5327.png#pic_center)]
2、当程序运行完,离开代码作用域时:
1、main.m类中的person指针(在栈中)被销毁。
2、但此时,block结构体对象和Person类存在互相引用的问题,而且是强引用,导致这两个对象互相持有,无法被销毁。
其结构如下图:
简而言之,就是Person对象中的_block成员变量持有了Block结构体,而Block结构体中也有一个成员变量(self)持有了Person对象。
1.3、总结
对象强引用Block,Block强引用对象。两个强引用,你引用我,我引用你,导致内存无法释放。
如下图:
二、循环引用的解决
2.1、思路分析
那具体又是怎么操作呢,我们在拿上面的循环引用图,分析一下:
已知【1、2】两个引用都是强引用,我们只要把其中一个引用设置成弱引用就可以了。那么设置哪一条为弱引用更合理呢?
在代码的实现过程中,我们希望不管什么时候引用Person类的block变量他都是没有问题的,只要Person没有被释放,block结构体也不能被释放。所以线条【2】最好时强引用关系。那么只能将线条【1】设置成弱引用关系(用虚线表示),如下图:
2.2、解决方案
2.1、ARC编译环境
1、用__weak、__unsafe_unretained解决
__weak:不会产生强引用
指向的对象销毁时,自己会主动销毁,变成 nil。
__unsafe_unretained:不会强引用,不安全。
指向的对象销毁时,自己不会主动销毁,会变成野指针。(虽然能解决问题,因为他不安全,不推荐使用)
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *person = [[Person alloc] init]; person.age = 10; __weak Person *weakP = person; // 跟上一句代码一个作用,使用跟方便 // __weak typeof(person) weakP = person; person.block = ^{ NSLog(@"-------:%d",weakP.age); }; NSLog(@"-------::::::::---------"); } return 0; }
底层结构:
打印日志:
block-01[14499:3644589] -------::::::::--------- block-01[14499:3644589] ---------Person__dealloc Program ended with exit code: 0
此时Person对象被销毁了。
2、用__block解决
注意:必须调用block、block函数执行完之后必须将对象置为nil。
示例代码
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { __block Person *person = [[Person alloc] init]; person.age = 10; person.block = ^{ NSLog(@"-------:%d",person.age); // 2、函数执行完成之后,必须将对象清空 person = nil; }; // 1、必须执行block函数 person.block(); NSLog(@"-------::::::::---------"); } return 0; }
缺点:
1、代码量多了。
2、必须要执行block函数(如果都没有执行block函数,那么它就一直存在,导致内存泄漏)
综上,最好的解决方案使用 __weak 引用。
2.2、MRC编译环境
1、用__unsafe_unretained解决
因为MRC编译环境下是不支持 __weak 弱指针修饰的。
代码示例
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { __unsafe_unretained Person *person = [[Person alloc] init]; person.age = 10; person.block = ^{ NSLog(@"-------:%d",person.age); }; person.block(); [person release]; NSLog(@"-------::::::::---------"); } return 0; }
2、用__block解决
在之前我们知道,__block修饰符 ARC时会retain,MRC时不会retain。
那么在MRC时,可以解决循环引用问题。
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { __block Person *person = [[Person alloc] init]; person.age = 10; person.block = ^{ NSLog(@"-------:%d",person.age); }; person.block(); [person release]; NSLog(@"-------::::::::---------"); } return 0; }
打印日志:
2022-05-09 13:29:02.145827+0800 block-01[18252:3704010] -------:10 2022-05-09 13:29:02.146321+0800 block-01[18252:3704010] ---------Person__dealloc 2022-05-09 13:29:02.146382+0800 block-01[18252:3704010] -------::::::::--------- Program ended with exit code: 0
引入弱引用之前:
引入弱引用之后: