Block 循环引用

Posted EchoHG

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Block 循环引用相关的知识,希望对你有一定的参考价值。

 

引用一

1 [self.teacher requestData:^(NSData *data) {
2     self.name = @"case";
3 }];

此种情况是最常见的循环引用导致的内存泄露了,在这里,self强引用了teacher, teacher又强引用了一个block,而该block在回调时又调用了self,会导致该block又强引用了self,造成了一个保留环,最终导致self无法释放。

self -> teacher -> block -> self

解决方法

1 __weak typeof(self) weakSelf = self;
2     [self.teacher requestData:^(NSData *data) {
3         typeof(weakSelf) strongSelf = weakSelf;
4        strongSelf.name = @"case";
5     }];

通过__weak的修饰,先把self弱引用(默认是强引用,实际上self是有个隐藏的__strong修饰的),然后在block回调里用weakSelf,这样就会打破保留环,从而避免了循环引用,如下:

self -> teacher -> block -> weakSelf

注意:一般会在block回调里再强引用一下weakSelf(typeof(weakSelf) strongSelf = weakSelf;),因为__weak修饰的都是存在栈内,可能随时会被系统释放,造成后面调用weakSelf时weakSelf可能已经是nil了,后面用weakSelf调用任何代码都是无效的。

 

引用二

代码

ViewController.m

技术分享图片
 1 #import "ViewController.h"
 2 #import "HYBAController.h"
 3 
 4 @interface ViewController ()
 5 
 6 @property (nonatomic, strong) UIButton *button;
 7 @property (nonatomic, strong) HYBAController *vc;
 8 
 9 @end
10 
11 @implementation ViewController
12 
13 - (void)goToNext {
14   __weak __typeof(self) weakSelf = self;
15   HYBAController *vc = [[HYBAController alloc] initWithCallback:^{
16     [weakSelf.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
17   }];
18 //  self.vc = vc;
19   [self.navigationController pushViewController:vc animated:YES];
20 }
21 
22 
23 
24 - (void)viewDidLoad {
25   [super viewDidLoad];
26   
27   UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
28   [button setTitle:@"进入下一个界面" forState:UIControlStateNormal];
29   button.frame = CGRectMake(50, 200, 200, 45);
30   button.backgroundColor = [UIColor redColor];
31   [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
32   [button addTarget:self action:@selector(goToNext) forControlEvents:UIControlEventTouchUpInside];
33   [self.view addSubview:button];
34   self.button = button;
35   
36   self.title = @"ViewController";
37 
38 }
39 
40 @end
View Code

 

HYBAController.h

1 #import <UIKit/UIKit.h>
2 
3 typedef void(^HYBCallbackBlock)();
4 
5 @interface HYBAController : UIViewController
6 
7 - (instancetype)initWithCallback:(HYBCallbackBlock)callback;
8 
9 @end

HYBAController.m

技术分享图片
 1 #import "HYBAController.h"
 2 #import "HYBAView.h"
 3 
 4 @interface HYBAController()
 5 
 6 @property (nonatomic, copy) HYBCallbackBlock callbackBlock;
 7 
 8 @property (nonatomic, strong) HYBAView *aView;
 9 @property (nonatomic, strong) id currentModel;
10 
11 @end
12 
13 @implementation HYBAController
14 
15 - (instancetype)initWithCallback:(HYBCallbackBlock)callback {
16   if (self = [super init]) {
17     self.callbackBlock = callback;
18   }
19   
20   return self;
21 }
22 
23 - (void)viewDidLoad {
24   [super viewDidLoad];
25   
26   self.title = @"HYBAController";
27   self.view.backgroundColor = [UIColor whiteColor];
28   
29   __block __weak __typeof(_currentModel) weakModel = _currentModel;
30   self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
31     // 假设要更新model
32     weakModel = model;
33 //      self.currentModel = model;
34   }];
35   // 假设占满全屏
36   self.aView.frame = self.view.bounds;
37   [self.view addSubview:self.aView];
38   self.aView.backgroundColor = [UIColor whiteColor];
39   
40 }
41 
42 - (void)viewDidAppear:(BOOL)animated {
43   [super viewDidAppear:animated];
44   
45   NSLog(@"进入控制器:%@", [[self class] description]);
46 }
47 
48 - (void)dealloc {
49   NSLog(@"%@-->控制器被dealloc", [[self class] description]);
50 }
51 
52 @end
View Code

 

 

HYBAView.h

1 #import <UIKit/UIKit.h>
2 
3 typedef void(^HYBFeedbackBlock)(id model);
4 
5 @interface HYBAView : UIView
6 
7 - (instancetype)initWithBlock:(HYBFeedbackBlock)block;
8 
9 @end

HYBAView.m

技术分享图片
 1 #import "HYBAView.h"
 2 
 3 @interface HYBAView ()
 4 
 5 @property (nonatomic, copy) HYBFeedbackBlock block;
 6 
 7 @end
 8 
 9 @implementation HYBAView
10 
11 - (void)dealloc {
12   NSLog(@"dealloc: %@", [[self class] description]);
13 }
14 
15 - (instancetype)initWithBlock:(HYBFeedbackBlock)block {
16   if (self = [super init]) {
17     self.block = block;
18     
19     UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
20     [button setTitle:@"反馈给controller" forState:UIControlStateNormal];
21     button.frame = CGRectMake(50, 200, 200, 45);
22     button.backgroundColor = [UIColor redColor];
23     [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
24     [button addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside];
25     [self addSubview:button];
26   }
27   
28   return self;
29 }
30 
31 - (void)feedback {
32   if (self.block) {
33     // 传模型回去,这里没有数据,假设传nil
34     self.block(nil);
35   }
36 }
37 
38 @end
View Code

以上是正常运行,不存在内存泄露的代码,下面进行细致讨论情况

场景一:Controller之间block传值

 1 @interface ViewController ()
 2  
 3 // 引用按钮只是为了测试
 4 @property (nonatomic, strong) UIButton *button;
 5 // 只是为了测试内存问题,引用之。在开发中,有很多时候我们是
 6 // 需要引用另一个控制器的,因此这里模拟之。
 7 @property (nonatomic, strong) HYBAController *vc;
 8  
 9 @end
10  
11 // 点击button时
12 - (void)goToNext {
13   HYBAController *vc = [[HYBAController alloc] initWithCallback:^{
14     [self.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
15   }];
16   self.vc = vc;
17   [self.navigationController pushViewController:vc animated:YES];
18 }

 原因:

这里形成了两个环:

  • ViewController->强引用了属性vc->强引用了callback->强引用了ViewController

  • ViewController->强引用了属性vc->强引用了callback->强引用了ViewController的属性button 

解决方案:

不声明vc属性或者将vc属性声明为weak引用的类型,在callback回调处,将self.button改成weakSelf.button,也就是让callback这个block对viewcontroller改成弱引用。如就是改成如下,内存就可以正常释放了:

1 - (void)goToNext {
2   __weak __typeof(self) weakSelf = self;
3   HYBAController *vc = [[HYBAController alloc] initWithCallback:^{
4     [weakSelf.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
5   }];
6 //  self.vc = vc;
7   [self.navigationController pushViewController:vc animated:YES];
8 }

 笔者尝试过使用Leaks检测内存泄露,但是全是通过,一个绿色的勾,让你以为内存处理得很好了,实际上内存并得不到释放。

针对这种场景,给大家提点建议:

 在控制器的生命周期viewDidAppear里打印日志:

1 - (void)viewDidAppear:(BOOL)animated {
2   [super viewDidAppear:animated];
3  
4   NSLog(@"进入控制器:%@", [[self class] description]);
5 }

在控制器的生命周期dealloc里打印日志

1 - (void)dealloc {
2   NSLog(@"控制器被dealloc: %@", [[self class] description]);
3 }

 

 

场景二: Controller与View之间Block传值

定义一个view,用于与Controller交互。当点击view的按钮时,就会通过block回调给controller,也就反馈到控制器了,并将对应的数据传给控制器以记录

技术分享图片
 1 typedef void(^HYBFeedbackBlock)(id model);
 2  
 3 @interface HYBAView : UIView
 4  
 5 - (instancetype)initWithBlock:(HYBFeedbackBlock)block;
 6  
 7 @end
 8  
 9 @interface HYBAView ()
10  
11 @property (nonatomic, copy) HYBFeedbackBlock block;
12  
13 @end
14  
15 @implementation HYBAView
16  
17 - (void)dealloc {
18   NSLog(@"dealloc: %@", [[self class] description]);
19 }
20  
21 - (instancetype)initWithBlock:(HYBFeedbackBlock)block {
22   if (self = [super init]) {
23     self.block = block;
24  
25     UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
26     [button setTitle:@"反馈给controller" forState:UIControlStateNormal];
27     button.frame = CGRectMake(50, 200, 200, 45);
28     button.backgroundColor = [UIColor redColor];
29     [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
30     [button addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside];
31     [self addSubview:button];
32   }
33  
34   return self;
35 }
36  
37 - (void)feedback {
38   if (self.block) {
39     // 传模型回去,这里没有数据,假设传nil
40     self.block(nil);
41   }
42 }
43  
44 @end
View Code

 

HYBAController,增加了两个属性,在viewDidLoad时,创建了aView属性

技术分享图片
 1 @interface HYBAController()
 2  
 3 @property (nonatomic, copy) HYBCallbackBlock callbackBlock;
 4  
 5 @property (nonatomic, strong) HYBAView *aView;
 6 @property (nonatomic, strong) id currentModel;
 7  
 8 @end
 9  
10 @implementation HYBAController
11  
12 - (instancetype)initWithCallback:(HYBCallbackBlock)callback {
13   if (self = [super init]) {
14     self.callbackBlock = callback;
15   }
16  
17   return self;
18 }
19  
20 - (void)viewDidLoad {
21   [super viewDidLoad];
22  
23   self.title = @"HYBAController";
24   self.view.backgroundColor = [UIColor whiteColor];
25  
26   self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
27     // 假设要更新model
28     self.currentModel = model;
29   }];
30   // 假设占满全屏
31   self.aView.frame = self.view.bounds;
32   [self.view addSubview:self.aView];
33   self.aView.backgroundColor = [UIColor whiteColor];
34 }
35  
36 - (void)viewDidAppear:(BOOL)animated {
37   [super viewDidAppear:animated];
38  
39   NSLog(@"进入控制器:%@", [[self class] description]);
40 }
41  
42 - (void)dealloc {
43   NSLog(@"控制器被dealloc: %@", [[self class] description]);
44 }
45  
46 @end
View Code

 

 

原因,形成的环:

  • vc->aView->block->vc(self)

  • vc->aView->block->vc.currentModel

解决的办法可以是:在创建aView时,block内对currentModel的引用改成弱引用:

1 __weak __typeof(self) weakSelf = self;
2 self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
3     // 假设要更新model
4     weakSelf.currentModel = model;
5 }];

 

 

很多类似这样的代码,直接使用成员变量,而不是属性,然后他们以为这样就不会引用self,也就是控制器,从而不形成环:

技术分享图片
1 self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
2     // 假设要更新model
3     _currentModel = model;
4 }];
View Code

 

以上是关于Block 循环引用的主要内容,如果未能解决你的问题,请参考以下文章

block-循环引用

block本质探寻八之循环引用

【OC语法】block的循环引用

block的用法和循环引用

Block的循环引用详解

block中self会造成循环引用问题