前言
本文不會詳細介紹
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.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();複製程式碼
那這又是什麼原因呢?我們繼續畫個記憶體圖來看看:
額~ 這圖有點亂啊,那就來一步一步分析,分析完之後上面所有的問題就會迎面而解!
- 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物件
所以全部銷燬!
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
也能解決迴圈引用,那麼可不可以weak
與strong
一起使用呢?我找了沒有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
解決迴圈引用的問題,有更好的辦法還請大神多多指教,如果有錯誤的地方幫忙糾正,非常感謝!