Swift與OC真正去理解Block解決迴圈引用的技巧

判若兩人丶發表於2019-02-24

前言

本文不會詳細介紹Block(閉包)使用,網上也有很多詳細的介紹。我們使用Block經常要注意迴圈引用問題,在很早以前我只用到了__weak並不知道__strong用的有啥意義存在。後來遇到坑了才明白其中的真理!之前文章中也提到這個問題,僅僅是講了使用層面,並沒有去講如何理解其中的道理,接下來我們來理解一下。

目錄

1.OC中Block的迴圈引用

我建立一個LRShop類,其中有下面2個屬性:

@property (nonatomic, copy)NSString *string;
@property (nonatomic, copy)void(^myBlock)();複製程式碼

首先我們從基礎一步一步去理解,先分析下面的程式碼:

LRShop *shop = [[LRShop alloc]init];
    shop.string = @"welcome to our company";
    shop.myBlock = ^{
        NSLog(@"%@",shop.string);
    };
    shop.myBlock();複製程式碼

如果block程式碼塊的內部,使用了外面的強引用shop物件(也就是shop.myBlock程式碼塊內部使用了NSLog(@"%@",shop.string);),block程式碼塊的內部會自動產生一個強引用,引用著shop物件!所以上面的shop不會被銷燬,造成迴圈引用!下面畫一張圖方便去理解:

圖中的每條橙色的線都是強引用。shop指向著LRShop物件,內部myBlock指向著Block程式碼塊string指向著@"",最後Block程式碼塊指向著LRShop物件

weak的使用

解決迴圈引用的問題我們第一個會用到__weak我這裡宣告瞭一個巨集,
如果不明白這個巨集可以看這篇文章中的第8點:

#define LRWeakSelf(type)  __weak typeof(type) weak##type = type;

 LRWeakSelf(shop);
    shop.myBlock = ^{
        NSLog(@"%@",weakshop.string);
    };
    shop.myBlock();複製程式碼

Block外部宣告瞭一個弱引用,在內部使用就不會造成迴圈引用,所以如果block程式碼塊的內部,使用了外面宣告的的弱引用weakshop物件(也就是shop.myBlock程式碼塊內部使用了NSLog(@"%@",weakshop.string);),block程式碼塊的內部會自動產生一個弱引用,引用著shop物件!我們繼續來看下記憶體圖:

總結:Block內部使用外部的一個物件,如果外部物件是強引用那麼內部會自動生成一個強引用,引用著外部物件。如果外部物件是弱引用那麼內部會自動生成一個弱引用,引用著外部物件。如果還是有點迷茫我們最後在舉一個例子:

    self.myName = @"我的名字是傑克!";
    LRShop *shop = [[LRShop alloc]init];
           shop.myBlock = ^{
                NSLog(@"%@",self.myName);
    };
    shop.myBlock();複製程式碼

上面的程式碼會不會造成迴圈引用呢?答案是不會的,首先self.myNameViewController控制器的一個屬性,Block內部使用外部的self.myName,外部的self.myName強引用那麼內部會自動生成一個強引用引用著self.myNameBlock內部強引用self.myName,但是self.myName沒有強引用Block!說白了就是粉絲與明星的關係,粉絲(Block)單方面追求明星(self.myName),但是隨便粉絲怎麼單方面的追求,明星都不搭理粉絲!

weak 與 strong 的使用

我們先看看下面程式碼:

//2個巨集
#define LRWeakSelf(type)  __weak typeof(type) weak##type = type;
#define LRStrongSelf(type)  __strong typeof(type) type = weak##type;

------------------------------------------------------

     LRShop *shop = [[LRShop alloc]init];
    shop.string = @"welcome to our company";

    LRWeakSelf(shop);
    shop.myBlock = ^{
        LRStrongSelf(shop)
        NSLog(@"%@",shop.string);
    };
    shop.myBlock();複製程式碼

表面來看外部一個弱引用,內部一個強引用那不是跟沒寫一樣麼?我們要理解一個問題LRStrongSelf(shop)Block內部的強引用,而不是外部強引用。所以Block內部宣告的強引用不管怎麼訪問都是不會干擾外部的物件,也不會自動產生一個強引用。所以沒有迴圈引用,也能輸出shop.string看著跟之前講的僅僅使用__weak沒什麼區別,那我們在來看看下面的程式碼:

僅僅使用LRWeakSelf(shop);並且在myBlock中增加一個延遲2秒在輸出就會出現問題, 雖然物件銷燬了, 輸出的值卻是null

//弱引用
 LRWeakSelf(shop);
    shop.myBlock = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakshop.string);
        });
    };
    shop.myBlock();複製程式碼

如果LRWeakSelf(shop);LRStrongSelf(shop);一起使用輸出的shop.string有值,物件也銷燬了:

LRWeakSelf(shop);
    shop.myBlock = ^{
        LRStrongSelf(shop)
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",shop.string);
        });
    };
    shop.myBlock();複製程式碼

那這又是什麼原因呢?我們繼續畫個記憶體圖來看看:

額~ 這圖有點亂啊,那就來一步一步分析,分析完之後上面所有的問題就會迎面而解!

  • 1.LRShop *shop = [[LRShop alloc]init];這行程式碼一執行就會出現圖中第1條強引用的線,引用著LRShop物件
  • 2.LRWeakSelf(shop);這行程式碼一執行就會出現圖中第2條弱引用的線,引用著LRShop物件,第3條線我們可以跳過不用看。
  • 3.shop.myBlock = ^{}這行Block程式碼一執行就會出現圖中第4條強引用的線。
  • 4.之後會執行shop.myBlock();這行程式碼,回撥到Block程式碼塊中,然後會執行LRStrongSelf(shop)這行程式碼,在執行這行程式碼前會自動產生第5條弱引用的線引用著LRShop物件(原因:Block內部使用外部的一個物件,如果外部物件是弱引用那麼內部會自動生成一個弱引用,引用著外部物件),最後就產生第6條強引用的線,引用著LRShop物件
  • 5.dispatch_after這行程式碼一執行就會出現圖中第7條GCD系統內部強引用的線,引用著dispatch_after
  • 6.由於GCDdispatch_after程式碼塊內部用到NSLog(@"%@",shop.string);用到了外部的強引用物件shop(原因:Block內部使用外部的一個物件,如果外部物件是強引用那麼內部會自動生成一個強引用,引用著外部物件。)所以就會出現圖中第8條強引用的線,引用著LRShop物件

上面6條是如何建立的,下面是如何釋放的:

  • 1.dispatch_afterBlock內部會延遲2秒執行,並且不會阻塞執行緒,所以任務會一直往下走,當shop.myBlock = ^{}Block大括號執行完,內部的區域性變數LRStrongSelf(shop)就會銷燬,第6條線就會銷燬。
  • 2.最終- (void)viewDidLoad {}的大括號也會執行完,所以LRShop *shop = [[LRShop alloc]init];LRWeakSelf(shop);區域性變數也會銷燬,第1條線與第2條線都會銷燬。
  • 3.現在只剩下GCD的第8條線強引用著LRShop物件,所以LRShop物件沒有銷燬,只有等待的2秒結束後,因為LRShop物件沒有死所以輸出有值,然後GCD系統內部不會再去強引用dispatch_afterBlock,首先第7條線銷燬,第8條線在銷燬。最後沒有人強引用LRShop物件所以全部銷燬!

2.Swift 閉包

在Swift中解決閉包迴圈引用有三種辦法我們來看看:

  • 1.weak var weakShop = shop方式解決迴圈引用,在平時開發中我們不用這個方法,用起來很麻煩!
    let shop : LRShop = LRShop()
    weak var weakShop = shop
    shop.myBlock = {(str : String) -> () in
        weakShop?.string = str
        print((weakShop?.string)!)
    }
    shop.myBlock!(str: "哈嘍,你好!")複製程式碼
  • 2.[unowned shop]方式解決迴圈引用,在平時開發中我們不用這個方法,這個方法是很危險的!
    let shop : LRShop = LRShop()    
    shop.myBlock = {[unowned shop] (str : String) -> () in
         shop.string = str
         print(shop.string!)
     }
     shop.myBlock!(str: "哈嘍,你好!")複製程式碼
  • 3.[weak self]方式解決迴圈引用,在平時開發中我們經常用這個方法,這個方式只是一種簡便的寫法!
    let shop : LRShop = LRShop()
    shop.myBlock = {[weak shop] (str : String) -> () in
            shop?.string = str
            print((shop?.string)!)
    }
    shop.myBlock!(str: "哈嘍,你好!")複製程式碼

輸出結果都能解決迴圈引用問題,下圖deinit相當OC中的dealloc方法:

那我們已經知道Swift中用weak也能解決迴圈引用,那麼可不可以weakstrong一起使用呢?我找了沒有strong這個關鍵字,那我們該如何解決下面延遲2秒後在執行任務的問題呢:

let shop : LRShop = LRShop()

    shop.myBlock = {[weak shop] (str : String) -> () in
            //時間設定
            let time: NSTimeInterval = 2.0
            //GCD:延遲2秒
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                    Int64(time * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
                        shop?.string = str
                        print((shop?.string))
            }
        }
        shop.myBlock!(str: "哈嘍,你好!")複製程式碼

我覺得既然沒有strong那肯定會有其他辦法來解決這個問題,既然只缺少一個強引用,那我就宣告一個強引用給他用:

let strongShop = shop;複製程式碼

上面程式碼就是我在閉包內部宣告的一個strongShop強引用,詳細程式碼如下:

    let shop : LRShop = LRShop()

    shop.myBlock = {[weak shop] (str : String) -> () in
          //強引用
        let strongShop = shop;

        //時間設定
        let time: NSTimeInterval = 2
        //GCD:延遲2秒
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
            Int64(time * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
                //賦值
                strongShop?.string = str
                //列印輸出
                print((strongShop?.string)!)
        }
    }
    shop.myBlock!(str: "哈嘍,你好!")複製程式碼

不管在OC中還是Swift中我們都解決了Block(閉包)中如何優雅的解決迴圈引用問題,並且也瞭解了造成迴圈引用的記憶體表現形式。上面在Swift解決迴圈引用的問題,有更好的辦法還請大神多多指教,如果有錯誤的地方幫忙糾正,非常感謝!

相關文章