前言
本文不會詳細介紹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不會被銷毀,造成循環引用!下面畫一張圖方便去理解:
圖中的每條橙色的線都是強引用。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.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();
如果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();
那這又是什麼原因呢?我們繼續畫個內存圖來看看:
額~ 這圖有點亂啊,那就來一步一步分析,分析完之後上面所有的問題就會迎面而解!
LRShop *shop = [[LRShop alloc]init];這行代碼一執行就會出現圖中第1條強引用的線,引用著LRShop對象。
LRWeakSelf(shop);這行代碼一執行就會出現圖中第2條弱引用的線,引用著LRShop對象,第3條線我們可以跳過不用看。
shop.myBlock = ^{}這行Block代碼一執行就會出現圖中第4條強引用的線。
之後會執行shop.myBlock();這行代碼,回調到Block代碼塊中,然後會執行LRStrongSelf(shop)這行代碼,在執行這行代碼前會自動產生第5條弱引用的線引用著LRShop對象(原因:Block內部使用外部的一個對象,如果外部對象是弱引用那麼內部會自動生成一個弱引用,引用著外部對象),最後就產生第6條強引用的線,引用著LRShop對象。
dispatch_after這行代碼一執行就會出現圖中第7條GCD系統內部強引用的線,引用著dispatch_after。
由於GCD的dispatch_after代碼塊內部用到NSLog(@"%@",shop.string);用到了外部的強引用對象shop(原因:Block內部使用外部的一個對象,如果外部對象是強引用那麼內部會自動生成一個強引用,引用著外部對象。)所以就會出現圖中第8條強引用的線,引用著LRShop對象。
上面6條是如何創建的,下面是如何釋放的:
dispatch_after的Block內部會延遲2秒執行,並且不會阻塞線程,所以任務會一直往下走,當shop.myBlock = ^{}的Block大括號執行完,內部的局部變量LRStrongSelf(shop)就會銷毀,第6條線就會銷毀。
最終- (void)viewDidLoad {}的大括號也會執行完,所以LRShop *shop = [[LRShop alloc]init];與 LRWeakSelf(shop);局部變量也會銷毀,第1條線與第2條線都會銷毀。
現在只剩下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方法:
那我們已經知道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解決循環引用的問題,有更好的辦法還請大神多多指教,如果有錯誤的地方幫忙糾正,非常感謝!