你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS閉包循環引用精講

iOS閉包循環引用精講

編輯:IOS開發基礎

1.jpg

前言

本篇文章精講iOS開發中使用Block時一定要注意內存管理問題,很容易造成循環引用。本篇文章的目標是幫助大家快速掌握使用block的技巧。

我相信大家都覺得使用block給開發帶來了多大的便利,但是有很多開發者對block內存管理掌握得不夠好,導致經常出現循環引用的問題。對於新手來說,出現循環引用時,是很難去查找的,因此通過Leaks不一定能檢測出來,更重要的還是要靠自己的分析來推斷出來。

聲景一:Controller之間block傳值

現在,我們聲明兩個控制器類,一個叫ViewController,另一個叫HYBAController。其中,ViewController有一個按鈕,點擊時會push到HYBAController下。

先看HYBAController:

// 公開了一個方法
- (instancetype)initWithCallback:(HYBCallbackBlock)callback;

// 非公開的屬性,這裡放出來只是告訴大家,HYBAController會對這個屬性強引用
@property (nonatomic, copy) HYBCallbackBlock callbackBlock;

下面分幾種小場景來看看循環引用問題:

@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屬性得不到釋放。分析其形成循環引用的原因(如下圖):

image

可以簡單說,這裡形成了兩個環:

  • ViewController->強引用了屬性vc->強引用了callback->強引用了ViewController

  • ViewController->強引用了屬性vc->強引用了callback->強引用了ViewController的屬性button

對於此這問題,我們要解決內存循環引用問題,可以這麼解:

不聲明vc屬性或者將vc屬性聲明為weak引用的類型,在callback回調處,將self.button改成weakSelf.button,也就是讓callback這個block對viewcontroller改成弱引用。如就是改成如下,內存就可以正常釋放了:

- (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裡打印日志:

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];

  NSLog(@"進入控制器:%@", [[self class] description]);
}
  • 在控制器的生命周期dealloc裡打印日志:

- (void)dealloc {
  NSLog(@"控制器被dealloc: %@", [[self class] description]);
}

這樣的話,只要日志沒有打印出來,說明內存得不到釋放,就需要學會分析內存引用問題了。

場景二:Controller與View之間Block傳值

我們先定義一個view,用於與Controller交互。當點擊view的按鈕時,就會通過block回調給controller,也就反饋到控制器了,並將對應的數據傳給控制器以記錄:

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屬性:

@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的引用問題上就可以了。

分析:如下圖所示:

image

所形成的環有:

  • vc->aView->block->vc(self)

  • vc->aView->block->vc.currentModel

解決的辦法可以是:在創建aView時,block內對currentModel的引用改成弱引用:

__weak __typeof(self) weakSelf = self;
self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
    // 假設要更新model
    weakSelf.currentModel = model;
}];

我見過很多類似這樣的代碼,直接使用成員變量,而不是屬性,然後他們以為這樣就不會引用self,也就是控制器,從而不形成環:

self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
    // 假設要更新model
    _currentModel = model;
}];

這是錯誤的理解,當我們引用了_currentModel時,它是控制器的成員變量,因此也就引用了控制器。要解決此問題,也是要改成弱引用:

__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下載地址

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved