iOS Block循環參考精講

來源:互聯網
上載者:User

標籤:

前言循環參考就是當self 擁有一個block的時候,在block 又調用self的方法。形成你中有我,我中有你,誰都無法將誰釋放的困局。又或者解決方案簡而言之就一句話的事情:__weak typeof (self) weakSelf = self; 

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

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

聲景一:Controller之間block傳值

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

先看HYBAController:

1234567  // 公開了一個方法- (instancetype)initWithCallback:(HYBCallbackBlock)callback; // 非公開的屬性,這裡放出來只是告訴大家,HYBAController會對這個屬性強引用@property (nonatomic, copy) HYBCallbackBlock callbackBlock; 

下面分幾種小情境來看看循環參考問題:

1234567891011121314151617181920  @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改成弱引用。如就是改成如下,記憶體就可以正常釋放了:

12345678910  - (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裡列印日誌:

 

1234567  - (void)viewDidAppear:(BOOL)animated {  [super viewDidAppear:animated];    NSLog(@"進入控制器:%@", [[self class] description]);} 

 

  • 在控制器的生命週期dealloc裡列印日誌:

 

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

這樣的話,只要日誌沒有列印出來,說明記憶體得不到釋放,就需要學會分析記憶體引用問題了。

情境二:Controller與View之間Block傳值

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

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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748  @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的引用改成弱引用:

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

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

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

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

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

這裡還要加上__block哦!

類比循環參考

假設下面如此寫代碼,是否出現記憶體得不到釋放問題?(其中,controller屬性都是強引用聲明的)

12345678  @autoreleasepool {  A *aVC = [[A alloc] init];  B *bVC = [[B allcok] init];  aVC.controller = bVC;  bVC.controller = aVC;} 

分析:

  • aVC->強引用了bVC
  • bVC->強引用了aVC

如果是這樣引用,就形成環了。aVC->bVC->aVC,這就形成了環。

看看是否形成環

 

1234567  NSMutableArray *mArray = [NSMutableArray arrayWithObjects:@"a",@"b",@"abc",nil];HYBAController *vc = [[HYBAController alloc] initWithCallback:^{    [mArray removeObjectAtIndex:0];}];[self.navigationController pushViewController:vc animated:YES]; 

 

  • vc ->強引用了block
  • block->強引用了myArray

因此,並沒有形成環。

如果上面將myArray給當前控制器強引用,也不會形成環:

  • vc ->強引用了block
  • block->強引用了self(當前控制器)

如果將vc設定成self.vc屬性且是強引用,才形成了循環參考。

寫在最後

本篇文章就講這麼多吧,寫本篇文章的目的是教大家如何分析記憶體是否形成環,只要懂得了如何去分析記憶體是否循環參考了,那麼在開發時一定會特別注意記憶體管理問題,而且尋找記憶體相關的問題的bug時,也比較輕鬆。

iOS Block循環參考精講

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.