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 {View Code14 __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
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 @endView 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 @endView 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 @endView 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 @endView 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 循環引用