1. 程式人生 > >iOS 什麼情況下weak self需要配合strong self使用

iOS 什麼情況下weak self需要配合strong self使用

前言

對於block和weak self這一對歡喜冤家,這篇博文預設你對此已經很熟悉了。
如果還不清楚block的幾種形式,以及在什麼情況下必須使用weak point什麼情況下可以使用strong point,可以參考《block之三種blcok》 以及《block之迴圈引用》

要理解下面的講解,也許你需要知道一個基礎知識:函式只有入棧後才能執行,執行完畢後出棧。

綜述

一般情況下我們使用__weak關鍵字定義一個強指標的弱引用;用__strong關鍵字定義一個弱指標的強引用,如下:

//__weak定義了一個指向self的弱指標weakS,當self釋放之後weakS被置為nil
__weak typeof(self) weakS = self; //這裡的strongS是個強指標,如果strongS初始化時self未釋放,則在strongS被引用過程中self都會被保留而不會釋放 __strong typeof(weakS) strongS = weakS;

使用weak關鍵字定義的指標不強引用物件,當變數釋放後會自動置為nil,這一點和assign有所區別。
使用strong關鍵字定義的指標會強引用物件,當strong物件自身釋放後,強引用物件才會釋放。

weak 情景1 - 使用weak很安全

在使用block的過程中,我們經常見到的情況是這樣:

//程式碼片段1
__weak typeof(self) weak = self; void (^block1)() = ^{ //為避免迴圈引用,在作為property的block中使用self的弱指標 NSLog(@" -- %@ --",weak); }; _v.block1 = block1;

當然這樣不會有什麼問題,列印結果無非就兩種,分別對應self釋放與未釋放,如下:

//self未釋放
2017-09-20 13:52:21.025 BlockDemo[4237:149469]  -- <BVC: 0x7fab00905040> --

//self已經釋放
2017-09-20 13:52:24.438 BlockDemo[4237:149469] -- (null) --

weak 情景2 - weak並不總是安全

但是,你也可能遇到如下情況:

//程式碼片段2
    __weak  typeof(self) weakS = self;
    void (^block1)() = ^{
        NSLog(@" -- %@ --",weakS);

        //這裡模擬一個dispatch_async,因為dispatch_after其實就是非同步的
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@" == %@ ==",weakS);
        });
    };
    _v.block1 = block1;

猜測一下會有幾種列印結果呢?
答案是3種,帶著疑惑請繼續往下看:

1、兩次列印self都沒有釋放
2017-09-20 13:47:33.090 BlockDemo[4141:145270]  -- <BVC: 0x7fde2d806940> --
2017-09-20 13:47:35.270 BlockDemo[4141:145270]  == <BVC: 0x7fde2d806940> ==

2、兩次列印self都釋放了
2017-09-20 13:49:09.955 BlockDemo[4141:145270]  -- (null) --
2017-09-20 13:49:12.154 BlockDemo[4141:145270]  == (null) ==

3、第一次列印self未釋放,第二次列印self釋放了
2017-09-20 13:50:02.526 BlockDemo[4190:147582]  -- <BVC: 0x7fcc98d02690> --
2017-09-20 13:50:04.526 BlockDemo[4190:147582]  == (null) ==

我們都知道,一般情況下(形如程式碼片段1)在block中使用weak self,如果block入棧時self未釋放,則在block出棧前self是不可能被釋放的,因此第一段程式碼只有兩種可能性。

程式碼片段2就是另一種情形了,我來解釋一下為什麼會出現兩次列印結果不一樣的情況:

  • 在block1入棧時self未釋放,因此第一個NSLog()能打印出self的記憶體地址;
  • 第二個NSLog()之所以打印出了(null),問題就在於block1出棧後,dispatch_after的程式碼塊還未入棧,而self恰恰就在block1出棧後,而dispatch_after任務塊入棧前釋放了,因此第二個NSLog()打印出了(null)。

dispatch_after明明就是寫在block1中的,為什麼block1出棧時,它還沒入棧呢?

  • 要想理解這個問題,首先要知道dispatch_after實際上是非同步的,即它的實現實際上是基於dispatch_async的,執行時會立即返回,並不會卡主執行緒。
  • dispatch_async的作用就是將它的block任務塊放進相應的佇列中(佇列其實就是任務列表),等待相關執行緒去執行,至於什麼時候執行(函式入棧後才能執行)存在不確定性。

至此,你是否已經明白了為什麼程式碼片段2中可能出現前後兩次列印結果不一致的情況?

後續

如果已經對程式碼片段2出現的第三種列印情況有所瞭解,應該也就知道了本篇博文所討論的問題答案了。

面對程式碼片段2的情況,我們只需要在block1中建立weakS的強引用即可保證block1和dispatch_after中的self相同(都未釋放/都是nil)

    __weak  typeof(self) weakS = self;
    void (^block1)() = ^{
        NSLog(@" -- %@ --",weakS);
        __strong typeof(weakS) strongS = weakS;
        //BVC * vc = weakS; 這種形式和__strong typeof(weakS)相同
        //strongS其實只是個區域性變數的強指標,它在作用域內不會被釋放

        //這裡模擬一個dispatch_async,dispatch_after其實就是非同步的
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@" == %@ ==",strongS);
        });
    };
    _v.block1 = block1;

總結

總結一下strong self配合weak self使用的條件

  1. block中用到了weak self
  2. block中包含非同步操作
  3. 非同步操作中用到了self

當以上三個條件都成立的時候,為了避免block和非同步操作中self(所有強指標)不一致,則應該在block中定義weak point的強引用,並在非同步操作中使用。

        //以下兩種方式定義都是可以的,只是習慣上使用__strong
        __strong typeof(weakS) strongS = weakS;
        BVC * strongS = weakS;

如有不妥之處,歡迎指正,謝謝!