iOS 開發中使用block的注意點
最近工作較忙,比較少寫部落格了,最近研究block的時候遇到一些小坑,就寫出來避免大家走我的舊路,做一個幫大家填坑的填坑人,進入主題。
相信大家對使用block是又愛又恨,愛它給開發帶來了多大的便利,但是又恨它是否循環引用掌握不夠準确,導緻經常出現循環引用的問題。對于新手來說,出現循環引用時,有時候通過Leaks不一定能檢測出來,更重要的還是得靠自己有一雙能看出哪裡循壞引用的一雙慧眼。
代碼與分析:
我們先定義一個view,用于與Controller互相傳值。當點選view的按鈕時,就會通過block回調給controller,也就回報到控制器了,并将對應的資料傳給控制器以記錄:
.h頭檔案
typedef void(^KTTestBlock)(id model);
@interface KTTestView : UIView
- (instancetype)initWithBlock:(KTTestBlock)block;
@end
.m檔案
@interface KTTestView ()
@property (nonatomic, copy) KTTestBlock block; // 私有屬性
@end
@implementation KTTestView
- (void)dealloc {
NSLog(@"dealloc: %@", [[self class] description]);
}
- (instancetype)initWithBlock:(KTTestBlock)block {
if (self = [super init]) {
self.block = block;
UIButton *button = {(
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setTitle:@"進行與controller的傳值" forState:UIControlStateNormal];
btn = CGRectMake(, , , );
btn.backgroundColor = [UIColor redColor];
[btn setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside];
return ban;
)}
}
return self;
}
- (void)feedback {
if (self.block) { // 需要判斷block是否有值
// 傳模型回去,這裡沒有資料,假設傳nil
self.block(nil);
}
}
@end
接下來看KTTestViewController,有兩個屬性,在viewDidLoad時,建立了aView屬性:
@interface KTTestViewController()
@property (nonatomic, strong) KTTestView *aView;
@property (nonatomic, strong) id currentModel;
@end
@implementation KTTestViewController
- (instancetype)initWithCallback:(KTCallbackBlock)callback {
if (self = [super init]) {
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"KTTestViewController";
self.view.backgroundColor = [UIColor whiteColor];
self.aView = [[KTTestView 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
很明顯,現在已經循環引用了
所形成的環有2個:
vc->aView->block->vc(self)
vc->aView->block->vc.currentModel
—-解決的辦法可以是:在建立aView時,block内對currentModel的引用改成弱引用:
__weak __typeof(self) weakSelf = self;
self.aView = [[KTTestView alloc] initWithBlock:^(id model) {
// 假設要更新model
weakSelf.currentModel = model;
}];
我見過很多類似這樣的代碼,直接使用成員變量,而不是屬性,然後他們以為這樣就不會引用self,也就是控制器,進而不形成環:
self.aView = [[KTTestView alloc] initWithBlock:^(id model) {
// 假設要更新model
_currentModel = model;
}];
這是錯誤的了解,當我們引用了_currentModel時,它是控制器的成員變量,是以也就引用了控制器。要解決此問題,也是要改成弱引用:
__block __weak __typeof(_currentModel) weakModel = _currentModel;
self.aView = [[KTTestView alloc] initWithBlock:^(id model) {
// 假設要更新model
weakModel = model;
}];
千萬要記得這裡還要加上__block哦!
暫時就講這麼多吧,目的是教大家如何分析記憶體是否形成環,隻要懂得了如何去分析記憶體是否循環引用了,那麼在開發時一定會特别注意記憶體管理問題,而且查找記憶體相關的問題的bug時,也就少走彎路,填好坑。