iOS 閉包中的[weak self]在什麼情況下需要使用,什麼情況下可以不加?


細講確實需要很長篇幅。不過我不同意樓上某位仁兄的「block 都要使用weak self」。至少我在試驗Malloc(一般的匿名block都是這種類型)類型的block時是不用weak self的。

如有錯誤還請勘誤,話不能說死不是?


如果block沒有直接或者間接被self存儲,就不會產生循環引用。

循環引用只要不依賴release打斷,也應該不會產生內存泄漏問題。

自己設計的模塊都可以在合適時機進行打斷。難就難在對系統類加擴展方法導致的循環引用。如果找得到合適的時機打斷,也是沒問題的。

另外有個簡單的方法可以繞過這個問題,如果self引用了一個block,block又需要調用self,可以把self通過參數回傳給block,這樣就不會產生循環引用了。block回傳的self可以聲明成id類型,這樣使用的時候可以在入參聲明具體self類型,避免顯式類型轉換,方便開發。

typedef void (^Block) (id selfRef);

Block block = ^(XXX *selfRef){

};


一句話解釋:只有當block直接或間接的被self持有時,才需要weak self。

// 這種情況沒必要
[self fetchDataWithSucess:^{
[self doSomething];
}];

//這種情況就有必要
self.onTapEvent = ^{
[self doSomething];
};


我跟大家分享想唐巧在YTKNetWorking里怎麼處理這件事的

YTKNetWorking的block是不需要weak引用的,

項目地址在這:yuantiku/YTKNetwork · GitHub

唐巧在文檔中也說明了,可以在block里直接用self。

原理其實也很簡單,在網路請求結束的時候,調用了這個方法把block置空,就打破了循環引用。

話說- -之前去面試,很多人都不知道這個技巧,自己封裝的時候,這麼做,就能少寫好多weakselfstrongself...最後還是要感謝巧神開源了這麼好的項目~~


如圖,A中有B,B中有A;則當我們初始化A之後,a.b和b.a形成了【引用循環】

這是我們不希望看到的,因為此時 A 和 B 兩個類的實例都不能被 deinit

這樣改寫之後, A 和 B 兩個類的實例就都可以被 deinit 了

因為我們在 var a前面加上了 weak,就是向編譯器表明我們不希望持有 a

至於題主所問的閉包中的情況,其實道理是一樣的,就是有無循環引用的問題

---------------至此問題回答完畢,以下為延伸---------------

顯然,標記為 weak 的成員是個 Optional 值,因為它引用的內容可能被釋放掉(deinit),那時它會從原來的值自動變成 nil,這也是 weak 友好的地方。

相對應的,還有一個不太友好的是 unowned,這個關鍵字在 Swift 中像是之前 OC 裡面的 unsafe_unretained,它不會變成 nil,而是會保持持有引用的狀態,即便在引用的內容被釋放掉之後,它也會保持一個無效的引用。

以上。


這裡引用《Effective Objective-C 2.0》一書中的講解:

33.以弱引用避免保留環

用unsafe_unretained修飾的屬性特質,其語義同assign特質等價。然而assign通常只用於「整體類型(int、float、結構體等)」,unsafe_unretained則多用於對象類型。這個詞本身就表明其所修飾的屬性可能無法安全使用(unsafe)

OC中還有一項與ARC相伴的運行期特性,可以令開發者使用弱引用:這就是weak屬性特質,它與unsafe_unretained的作用完全相同。

1)將某些引用設為weak,可避免出現「保留環」。

2)weak引用可以自動清空,也可以不自動清空。自動清空(autoniling)是隨著ARC而引入的新特性,有運行期系統來實現。在具備自動清空功能的弱引用上,可以隨著讀取其數據,因為這種引用不會指向已經回收過的對象;

40.用塊引用其所屬對象時不要出現保留環

如果設計API時用到completion handler這樣的回調塊,那麼很容易形成保留環,所以很容易形成保留環,所以必須意識到這個重要的問題。一般來說,只要適時清理掉環中的某個引用,即可解決此類問題,然而未必總有這種機會。在本例中,為由completion handler運行過後,方能解除保留環,若是completion handler一直不運行,那麼保留環就無法打破,於是內存就會泄露。

另外,ARC與MRC下Block的處理是不同的。在ARC下,由於__block抓取的變數一樣會被Block retain,所以必須用弱引用才可以解決循環引用問題,iOS 5之後可以直接使用__weak,之前則只能使用__unsafe_unretained了,__unsafe_unretained缺點是指針釋放後自己不會置空;

在非ARC下,顯然無法使用弱引用,這裡就可以直接使用__block來修飾變數,它不會被Block所retain的. 具體可以參考這篇博文:iOS: ARC和非ARC下使用Block屬性的問題

當然,推薦還是使用ARC,做一下這五道題你就明白為什麼推薦來管理Block了:Objective-C Blocks Quiz希望我的回答能幫到你!


分析個最常見的循環引用好了,MJRefresh,是最常用的下拉刷新控制項

MJRefreshAutoFooter *footer = [MJRefreshAutoFooter footerWithRefreshingBlock:^{

[weakSelf footerRefresh];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

[weakSelf.list.footer endRefreshing];

});

}];

self.list.footer = footer;

為什麼這裡要用weakSelf呢?

+ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock

{

MJRefreshFooter *cmp = [[self alloc] init];

cmp.refreshingBlock = refreshingBlock;

return cmp;

}

跳到footerWithRefreshingBlock里就知道了,這裡的refreshingBlock是屬於MJRefreshFooter的強引用屬性,最後footer會成為我們自己list的強引用屬性,

也就是說

self.list 強引用 footer, footer 強引用 refreshingBlock,如果refreshingBlock裡面強引用self,就成了循環引用,所以你要用weakSelf,破掉這個循環

另外多說一句,在block里最好用__strong class *strongSelf = weakSelf,防止block被釋放,造成野指針錯誤(BAD_ADDRESS_ACCESS)

RAC里提供了一對兒@weakifySelf和@strogifySelf宏,專門干這個事兒。


首先,block中為什麼會用到weakself是因為要避免循環引用,一旦出現循環引用那麼對象就會常駐內存。如果一個應用程序裡面你有很多循環引用,那麼內存佔用就會比較大,這當然是誰都不想看到的結果。那麼問題的重點就是:什麼時候會出現循環引用?先來看一個例子:

NSArray *anArray = @[@"1", @"2", @"3"];
[anArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[self doSomething:idx];
}];

這種情況下,block中retain了self,當block中的代碼被執行完後,self就會被ARC釋放。所以不需要處理weakself的情況。

再來看一個例子:

@interface aViewController ()
@property (nonatomic, strong) void(^aBlock)(id obj, NSUInteger idx, BOOL *stop);
@end

__weak aViewController *weakSelf = self;
self.aBlock = ^(id obj, NSUInteger idx, BOOL *stop) {
[weakSelf doSomething:idx];
}

這個例子的區別在於:block被self strong引用。所以結果就是block中引用了self,self引用了block。那麼這個時候,如果你不使用weakself,則self和block永遠都不會被釋放。

那麼是不是遇到block都要使用weakself呢?當然不是,而且如果全部使用weakself,會出現你想執行block中的代碼時,self已經被釋放掉了的情況。

另外,在處理weakself時,有兩種做法:__weak和__unsafe_unretained。兩種做法各有推薦,有的人覺得後者從字面上更好理解,而有的人覺得前者更加安全,因為self被釋放時會自動指向nil。有的人又說了,就是應該讓app崩潰才能發現問題所在。這個,看個人吧。


這是一個聽上去容易,實際上很難作對的事情。不能保證我總結的對。

總結:同步調用中一定可以用self,非同步調用中可能是self可能是weak self,當用self的時候要特別小心。

對block中的self進行retain操作的函數是block_copy,非同步調用的時候一般會把block部分所使用到的OC數據通過block_copy保存起來,等過一會再調用

比如非同步調用函數會對self進行retain操作

dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self doSomething];
});

另外同步調用函數函數不會對self進行retain操作

[array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
[self doSomething];
}];

非同步調用函數用self得注意什麼呢?

1. 如果self是永遠不會dealloc的數據部分:放心的用self吧,反正引用1萬次不dealloc和引用1次不dealloc沒有區別

2. 如果self是VC:這樣容易出現crash,比如

dispatch_async之後,VC立馬通過popViewController從程序UI樹中脫離,脫離完成之後,通過self調用UI,得到的界面層級混亂,tableview也可能因為沒有父窗口刷新引起各種奇怪的問題。

如果想了解更多比如為什麼 block變數定義不能用strong,只能用copy,建議了解block的實現,比如這裡:Block_copy的實現(32位機器)


em. 當多人協作開發的時候, 99.9%選擇__weak~~~ 有時候會選擇 __block.

直接引用self的有可能會被處女座給拖出去彈shi!


weak應該是用來解決循環引用的,看看基礎就能明白


推薦閱讀:

Python中後期綁定(late binding)是什麼意思?
為何前端面試官都喜歡問閉包?
如何通俗地解釋閉包的概念?

TAG:iOS開發 | iOS工程師 | Objective-C | 閉包 | Swift語言 |