前言 本篇文章精講iOS開發中使用Block時一定要註意記憶體管理問題,很容易造成迴圈引用。本篇文章的目標是幫助大家快速掌握使用block的技巧。 我相信大家都覺得使用block給開髮帶來了多大的便利,但是有很多開發者對block記憶體管理掌握得不夠好,導致經常出現迴圈引用的問題。對於新手來說,出現迴圈
前言
本篇文章精講iOS開發中使用Block時一定要註意記憶體管理問題,很容易造成迴圈引用。本篇文章的目標是幫助大家快速掌握使用block的技巧。
我相信大家都覺得使用block給開髮帶來了多大的便利,但是有很多開發者對block記憶體管理掌握得不夠好,導致經常出現迴圈引用的問題。對於新手來說,出現迴圈引用時,是很難去查找的,因此通過Leaks不一定能檢測出來,更重要的還是要靠自己的分析來推斷出來。
聲景一:Controller之間block傳值
現在,我們聲明兩個控制器類,一個叫ViewController,另一個叫HYBAController。其中,ViewController有一個按鈕,點擊時會push到HYBAController下。
先看HYBAController:
1 2 3 4 5 |
// 公開了一個方法
- (instancetype)initWithCallback:(HYBCallbackBlock)callback;
// 非公開的屬性,這裡放出來只是告訴大家,HYBAController會對這個屬性強引用
@property (nonatomic, copy) HYBCallbackBlock callbackBlock;
|
下麵分幾種小場景來看看迴圈引用問題:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@interface ViewController ()
// 引用按鈕只是為了測試
@property (nonatomic, strong) UIButton *button;
// 只是為了測試記憶體問題,引用之。在開發中,有很多時候我們是
// 需要引用另一個控制器的,因此這裡模擬之。
@property (nonatomic, strong) HYBAController *vc;
@end
// 點擊button時
- (void)goToNext {
HYBAController *vc = [[HYBAController alloc] initWithCallback:^{
[self.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
}];
self.vc = vc;
[self.navigationController pushViewController:vc animated:YES];
}
|
現在看ViewController這裡,這裡在block的地方形成了迴圈引用,因此vc屬性得不到釋放。分析其形成迴圈引用的原因(如下圖):
可以簡單說,這裡形成了兩個環:
-
ViewController->強引用了屬性vc->強引用了callback->強引用了ViewController
-
ViewController->強引用了屬性vc->強引用了callback->強引用了ViewController的屬性button
對於此這問題,我們要解決記憶體迴圈引用問題,可以這麼解:
不聲明vc屬性或者將vc屬性聲明為weak引用的類型,在callback回調處,將self.button改成weakSelf.button,也就是讓callback這個block對viewcontroller改成弱引用。如就是改成如下,記憶體就可以正常釋放了:
1 2 3 4 5 6 7 8 |
- (void)goToNext {
__weak __typeof(self) weakSelf = self;
HYBAController *vc = [[HYBAController alloc] initWithCallback:^{
[weakSelf.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
}];
// self.vc = vc;
[self.navigationController pushViewController:vc animated:YES];
}
|
筆者嘗試過使用Leaks檢測記憶體泄露,但是全是通過,一個綠色的勾,讓你以為記憶體處理得很好了,實際上記憶體並得不到釋放。
針對這種場景,給大家提點建議:
-
在控制器的生命周期viewDidAppear里列印日誌:
1 2 3 4 5 |
- (void)viewDidAppear:(BOOL)animated {
[ super viewDidAppear:animated];
NSLog(@ "進入控制器:%@" , [[self class] description]);
}
|
-
在控制器的生命周期dealloc里列印日誌:
1 2 3 |
- (void)dealloc {
NSLog(@ "控制器被dealloc: %@" , [[self class] description]);
}
|
這樣的話,只要日誌沒有列印出來,說明記憶體得不到釋放,就需要學會分析記憶體引用問題了。
場景二:Controller與View之間Block傳值
我們先定義一個view,用於與Controller交互。當點擊view的按鈕時,就會通過block回調給controller,也就反饋到控制器了,並將對應的數據傳給控制器以記錄:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
typedef void(^HYBFeedbackBlock)(id model);
@interface HYBAView : UIView
- (instancetype)initWithBlock:(HYBFeedbackBlock)block;
@end
@interface HYBAView ()
@property (nonatomic, copy) HYBFeedbackBlock block;
@end
@implementation HYBAView
- (void)dealloc {
NSLog(@ "dealloc: %@" , [[self class] description]);
}
- (instancetype)initWithBlock:(HYBFeedbackBlock)block {
if (self = [ super init]) {
self.block = block;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTitle:@ "反饋給controller" forState:UIControlStateNormal];
button.frame = CGRectMake(50, 200, 200, 45);
button.backgroundColor = [UIColor redColor];
[button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
[button addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void)feedback {
if (self.block) {
// 傳模型回去,這裡沒有數據,假設傳nil
self.block(nil);
}
}
@end
|
接下來看HYBAController,增加了兩個屬性,在viewDidLoad時,創建了aView屬性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
@interface HYBAController()
@property (nonatomic, copy) HYBCallbackBlock callbackBlock;
@property (nonatomic, strong) HYBAView *aView;
@property (nonatomic, strong) id currentModel;
@end
@implementation HYBAController
- (instancetype)initWithCallback:(HYBCallbackBlock)callback {
if (self = [ super init]) {
self.callbackBlock = callback;
}
return self;
}
- (void)viewDidLoad {
[ super viewDidLoad];
self.title = @ "HYBAController" ;
self.view.backgroundColor = [UIColor whiteColor];
self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
// 假設要更新model
self.currentModel = model;
}];
// 假設占滿全屏
self.aView.frame = self.view.bounds;
[self.view addSubview:self.aView];
self.aView.backgroundColor = [UIColor whiteColor];
}
- (void)viewDidAppear:(BOOL)animated {
[ super viewDidAppear:animated];
NSLog(@ "進入控制器:%@" , [[self class] description]);
}
- (void)dealloc {
NSLog(@ "控制器被dealloc: %@" , [[self class] description]);
}
@end
|
關於上一場景所講的迴圈引用已經解決了,因此我們這一小節的重點就放在controller與view的引用問題上就可以了。
分析:如下圖所示:
所形成的環有:
-
vc->aView->block->vc(self)
-
vc->aView->block->vc.currentModel
解決的辦法可以是:在創建aView時,block內對currentModel的引用改成弱引用:
1 2 3 4 5 |
__weak __typeof(self) weakSelf = self;
self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
// 假設要更新model
weakSelf.currentModel = model;
}];
|
我見過很多類似這樣的代碼,直接使用成員變數,而不是屬性,然後他們以為這樣就不會引用self,也就是控制器,從而不形成環:
1 2 3 4 |
self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
// 假設要更新model
_currentModel = model;
}];
|
這是錯誤的理解,當我們引用了_currentModel時,它是控制器的成員變數,因此也就引用了控制器。要解決此問題,也是要改成弱引用:
1 2 3 4 5 |
__block __weak __typeof(_currentModel) weakModel = _currentModel;
self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
// 假設要更新model
weakModel = model;
}];
|
這裡還要加上__block哦!
模擬迴圈引用
假設下麵如此寫代碼,是否出現記憶體得不到釋放問題?(其中,controller屬性都是強引用聲明的)
@autoreleasepool { A *aVC = [[A alloc] init]; B *bVC = [[B allcok] init]; aVC.controller = bVC; bVC.controller = aVC; }
分析:
aVC->強引用了bVC->強引用了aVC,因此形成了一個環,導致記憶體得不到釋放。
寫在最後
本篇文章就講這麼多吧,寫本篇文章的目的是教大家如何分析記憶體是否形成環,只要懂得瞭如何去分析記憶體是否迴圈引用了,那麼在開發時一定會特別註意記憶體管理問題,而且查找記憶體相關的問題的bug時,也比較輕鬆。
源代碼
本篇寫了個小demo來測試的,如果只看文章不是很明白的話,如果下載demo來運行看一看,可以幫助您加深對block記憶體引用問題的理解。
下載地址:標哥的GITHUB下載地址
問啊-定製化IT教育平臺,牛人一對一服務,有問必答,開發編程社交頭條 官方網站:www.wenaaa.com
QQ群290551701 聚集很多互聯網精英,技術總監,架構師,項目經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!