循环引用是什么
ARC已经出来很久了,自动释放内存的确很方便,但是在相亲app开发应用中,并非绝对安全绝对不会产生内存泄露。导致iOS对象无法按预期释放的一个无形杀手是——循环引用。循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是一个ViewController,则在dismiss或者pop之后其dealloc无法被调用,在频繁的push或者present之后内存暴增,然后APP就挂了。
iOS内存中的分区有:堆、栈、静态区。其中,栈和静态区是操作系统自己管理回收,不会造成循环引用。在堆中的相互引用无法回收,有可能造成循环引用。
循环引用的实质:多个对象相互之间有强引用,不能施放让系统回收。
解决循环引用一般是将 strong 引用改为 weak 引用。
循环引用场景
基础控制器代码:
#import "ViewController.h"
/// 前两种解决循环引用
typedef void(^CPBlock)(void);
/// 用于第三种解决循环引用
typedef void(^CPParamBlock)(ViewController *);
@interface ViewController ()
/// 前两种解决循环引用示例
@property(nonatomic, copy) CPBlock block;
/// 用于第三种解决循环引用示例
@property(nonatomic, copy) CPParamBlock paramBlock;
@property(nonatomic, copy) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"CPBlock";
}
@end
Block循环引用示例代码
Block 是我们常用到的,也是我们最要注意循环引用的。在此,先拿一个我们使用 Block 的例子:
//1. self持有block
self.block = ^(void){
// block持有self
NSLog(@"%@", self);
// block持有self的属性name
NSLog(@"%@", self.name);
};
- 当self自身要释放的时候, 就需要先释放block,
- 要释放block, 就要先把执行体内部block对self的持有先释放,
- self的retainCount != 0, self.block的retainCount != 0, 两者永远不会被释放, 造成循环引用
Delegate
delegate 属性的声明如下:
@property (nonatomic, weak) id <TestDelegate> delegate;
如果将 weak 改为 strong,则会造成循环引用
// self -> AViewController
BViewController *bVc = [BViewController new];
bVc = self;
[self.navigationController pushViewController: bVc animated:YES];
// 假如是 strong 的情况
// bVc.delegate ===> AViewController (也就是 A 的引用计数 + 1)
// AViewController 本身又是引用了 <BViewControllerDelegate> ===> delegate 引用计数 + 1
// 导致: AViewController <======> Delegate ,也就循环引用啦
解决循环应用
方法一. 弱引用方式解决相亲app开发的循环应用代码 __weak
-(void)resolveRetainCycle_1{
__weak typeof(self) weakSelf = self;
self.block = ^(void){
NSLog(@"%@", weakSelf);
// 如果block执行体内有耗时操作, 或者异步操作, 那么我们还需要优化循环引用的解决方案
// strongSelf为block执行体内局部变量, 在block执行体范围内使用, 当block被释放, strongSelf也会被自动释放
__strong typeof(weakSelf)strongSelf = weakSelf;
// 以此异步延迟2秒任务模拟耗时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf.name);
});
};
self.block();
}
方法二. 截获变量方式 __block
-(void)resolveRetainCycle_2{
// 截获当前ViewController
// 需要手动释放 vc = nil;
__block ViewController *vc = self;
self.block = ^(void){
// 以此异步延迟2秒任务模拟耗时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
vc = nil;
});
};
self.block();
}
方法三. 通讯传值方式
/// 解决循环引用方法三
-(void)resolveRetainCycle_3{
// 把self(实际为当前控制器: ViewController)作为一个临时变量传进block执行体
// 不会造成循环引用
self.paramBlock = ^(ViewController *vc){
// 以此异步延迟2秒任务模拟耗时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
});
};
self.paramBlock(self);
}
这样,相亲app开发就能解决循环引用的问题了。
声明:本文由云豹科技转发自麻蕊老师博客,如有侵权请联系作者删除