1. 程式人生 > >Block 循環引用

Block 循環引用

lock 解決方法 button DC str make 點擊 atom sda

引用一

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 循環引用