天天看點

iOS 開發中使用block的注意點

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時,也就少走彎路,填好坑。