你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> Swift與OC真正去理解Block解決循環引用的技巧

Swift與OC真正去理解Block解決循環引用的技巧

編輯:IOS開發基礎

前言

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

目錄

  • OC中Block的循環引用

1)__weak的使用

2)__weak 與 __strong 一起使用

  • Swift 閉包

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不會被銷毀,造成循環引用!下面畫一張圖方便去理解:

2353624-f9291a74dc88fea7.png

圖中的每條橙色的線都是強引用。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對象!我們繼續來看下內存圖:

33.png

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

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

上面的代碼會不會造成循環引用呢?答案是不會的,首先self.myName是ViewController控制器的一個屬性,Block內部使用外部的self.myName,外部的self.myName是強引用那麼內部會自動生成一個強引用引用著self.myName。Block內部強引用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();

2353624-0a71a7b33f4010c4.png

如果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();

33.png

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

2353624-7f1b4754ec8805ef.png

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

  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. 由於GCD的dispatch_after代碼塊內部用到NSLog(@"%@",shop.string);用到了外部的強引用對象shop(原因:Block內部使用外部的一個對象,如果外部對象是強引用那麼內部會自動生成一個強引用,引用著外部對象。)所以就會出現圖中第8條強引用的線,引用著LRShop對象。

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

  1. dispatch_after的Block內部會延遲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_after的Block,首先第7條線銷毀,第8條線在銷毀。最後沒有人強引用LRShop對象所以全部銷毀!

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方法:

2353624-3a2be2031873a49e.png

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

let shop : LRShop = LRShop()
9
    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解決循環引用的問題,有更好的辦法還請大神多多指教,如果有錯誤的地方幫忙糾正,非常感謝!

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