快年底了固阁,想對一些之前有點模糊的概念進行總結,所以寫了總結筆記。主要參考了這幾篇文章:
Block技術中的weak-strong
什么時候在 block 中不需要使用 weakSelf
為什么 weakSelf 需要配合 strong self 使用
block 什么時候需要構造循環(huán)引用
一.循環(huán)引用例子
#import "ViewController.h"
@interface ViewController(){
id _observer;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"%@", self);
}];
}
- (void)dealloc {
if (_observer) {
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
}
在這段代碼中,我們向通知中心注冊了一個觀察者,然后在dealloc
時殉了,解除該注冊,看起來很正常拟枚,但這里存在循環(huán)引用薪铜。
a.消息通知block引用了self,這里self對象被block保留一次
b._observer又retain該block的一份拷貝,通知中心又持有_observer.
c.只要_observer對象還沒有被解除注冊恩溅,block就會被通知中心一直持有隔箍,從而self就不會被釋放,dealloc也不會被調用
d.但我們有希望在dealloc中通過removeObserver來解除注冊以消除通知中心對_observer/block的保留次數(shù)脚乡。
e.同時_observer是self所在類中定義賦值蜒滩,被self retain
f.總結:因為self要想調用dealloc就必須等通知中心移除注冊,釋放掉_observer,但是要想通知中心移除注冊奶稠、釋放掉_observer就必須調用dealloc.這樣相互等待就變成了死循環(huán)俯艰。這里即便self不持有_observer也會產生循環(huán)引用問題,_observer只是為了告訴通知中心移除哪個注冊。
二锌订、解決方法:
1.通過weakSelf和strongSelf
@interface ViewController(){
id _observer;
}
@end
@implementation ViewController
#pragma mark --- life circle
- (void)viewDidLoad {
[super viewDidLoad];
__weak __typeof (self)weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf) {
NSLog(@"%@", strongSelf);
}
}];
}
- (void)dealloc {
if (_observer) {
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
}
在這段代碼中竹握,通過在block
之前定義對self
的弱引用,來解決了循環(huán)引用問題辆飘。
a.在block之前定義對self的一個弱引用weakSelf,因為是弱引用啦辐,所以self被釋放時weakSelf會變?yōu)閚il;
b.在block中引用該弱引用,考慮到多線程情況蜈项,通過強引用strongSelf來引用該弱引用昧甘,這時如果self不為nil就會retain self,以防止在block內部使用過程中self被釋放。
c.在block塊中使用該強引用strongSelf战得,注意對strongSelf進行nil檢測,因為多線程在弱引用weakSelf對強引用strongSelf賦值時庸推,弱引用weakSelf可能已經為nil了
d.強引用strongSelf在block作用域結束之后常侦,自動釋放。
三贬媒、block不需要使用weakSelf情況
當block
本身不被self
持有聋亡,而被別的對象持有,同時不產生循環(huán)引用的時候际乘,就不需要weakSelf
.最常見的代碼就是UIView
的動畫代碼坡倔。我們在使用UIView
的animateWithDuration:animations
方法做動畫的時候,并不需要使用weakSelf
, 因為引用的持有關系是:
a.UIView的某個負責動畫的對象持有了block
b.block持有了self
因為self并沒有持有block,所以就不存儲循環(huán)引用,因此就不需要使用weakSelf;
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 1;
}];
當動畫結束時罪塔,UIView
會結束持有這個block
投蝉,如果沒有別的對象持有block
的話,block
就會釋放掉征堪,從而block
就會釋放對于self
的持有瘩缆,整個內存引用關系被解除。
四.weakSelf 為什么需要strongSelf配合使用
在 block
中先寫一個 strong self佃蚜,其實是為了避免在block
的執(zhí)行過程中庸娱,突然出現(xiàn)self
被釋放的尷尬情況。通常情況下谐算,如果不這么做的話熟尉,還是很容易出現(xiàn)一些奇怪的邏輯,甚至閃退洲脂。
我們以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代碼舉例:
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf) {
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
}
};
如果沒有 strongSelf 的那行代碼斤儿,那么后面的每一行代碼執(zhí)行時,self
都可能被釋放掉了腮考,這樣很可能造成邏輯異常雇毫。
特別是當我們正在執(zhí)行 strongSelf.networkReachabilityStatusBlock(status);
這個 block 閉包時,如果這個block
執(zhí)行到一半時 self
釋放踩蔚,那么多半情況下會 Crash
棚放。
五.block需要構造循環(huán)引用
需要不使用weak self
的場景是:你需要構造一個循環(huán)引用,以便保證引用雙方都存在馅闽。比如你有一個后臺的任務飘蚯,希望任務執(zhí)行完后,通知另外一個實例福也。比如開源的YTKNetwork
網絡庫的源碼中局骤,就有這樣的場景。
在 YTKNetwork
庫中暴凑,我們的每一個網絡請求API
會持有回調的block
峦甩,回調的 block
會持有self
,而如果 self
也持有網絡請求API
的話现喳,我們就構造了一個循環(huán)引用凯傲。雖然我們構造出了循環(huán)引用,但是因為在網絡請求結束時嗦篱,網絡請求 API
會主動釋放對block
的持有冰单,因此,整個循環(huán)鏈條被解開灸促,循環(huán)引用就被打破了诫欠,所以不會有內存泄漏問題涵卵。代碼其實很簡單,如下所示:
// YTKBaseRequest.m
- (void)clearCompletionBlock {
// nil out to break the retain cycle.
self.successCompletionBlock = nil;
self.failureCompletionBlock = nil;
}
總結來說荒叼,解決循環(huán)引用問題主要有兩個辦法:
第一個辦法是「事前避免」轿偎,我們在會產生循環(huán)引用的地方使用 weak 弱引用,以避免產生循環(huán)引用甩挫。
第二個辦法是「事后補救」贴硫,我們明確知道會存在循環(huán)引用,但是我們在合理的位置主動斷開環(huán)中的一個引用伊者,使得對象得以回收英遭。
五. 即時不互相持有,也需要weakSelf和strongSelf情況
// 設置 網絡 請求
- (void)setupNetworkRequest {
[MBProgressHUD showLoading:nil toView:self.view];
__weak typeof(self) weakSelf = self;
[MSLogisticsListViewModel loadLogisticsInfoWithOrderNo:self.orderNo completion:^(LERequestResultType type, NSArray *dataArray) {
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[MBProgressHUD hideForView:strongSelf.view animated:YES];
if (type == LERequestResultTypeSuccess || type == LERequestResultTypeNoMore || dataArray.count > 0) {
strongSelf.logisticsDiverArray = dataArray;
[strongSelf.tableView hideBlankPage];
[strongSelf.tableView reloadData];
return ;
}
if (type == LERequestResultTypeNoNetWork) {
[strongSelf.tableView showBlankPageWithImage:networkTipsImage
tips:networkBrokenTips];
return ;
}
if (type == LERequestResultTypeServerError) {
[strongSelf.tableView showBlankPageWithImage:serverBusyTipsImage
tips:serverErrorTips];
return;
}
if (dataArray.count == 0) {
[strongSelf.tableView showBlankPageWithImage:logisticsTipsImage
tips:logisticsNullDataTips];
return ;
}
}
}];
}
比如這個由MSLogisticsListViewModel
發(fā)起的網絡請求亦渗,因為MSLogisticsListViewModel
持有block
挖诸,block
持有self
,但是self并不持有MSLogisticsListViewModel
,因此這里并不存在循環(huán)引用。那為什么也要用weakSelf
和strongSelf
.
因為當網絡請求回來后法精,這里可能會進行怎大量的數(shù)據(jù)處理和邏輯處理多律,如果數(shù)據(jù)處理和邏輯處理相對復雜,處理過程中可能會存在循環(huán)引用搂蜓,如果不用weakSelf
和strongSelf
狼荞,那么只有等到block
里面的內容執(zhí)行完畢之后,才會調用dealloc
方法帮碰,如果只是block
內部的處理過程相味,也存在循環(huán)引用,就會導致內存泄漏殉挽。
而如果使用了weakSelf
和strongSelf
丰涉,那么當該視圖銷毀的時候,如果block
還沒有回調斯碌,就會直接先調用dealloc
方法一死,再走block
,不過這是weakSelf
為nil
傻唾,這就能保證比如控制器viewController
進行pop
之后投慈,立即釋放。
沒有用weakSelf
和strongSelf
:
- (void)dealloc {
NSLog(@"dealloc method");
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延遲 回調");
[self.tableView reloadData];
NSLog(@"tableView 刷新");
});
}
pop viewController
打印結果:
FJChatMessageViewDemo[37477:780816] 延遲 回調
FJChatMessageViewDemo[37477:780816] tableView 刷新
FJChatMessageViewDemo[37477:780816] dealloc method
使用weakSelf
和strongSelf
:
- (void)dealloc {
NSLog(@"dealloc method");
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延遲 回調");
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[weakSelf.tableView reloadData];
NSLog(@"tableView 刷新");
}
});
}
pop viewController
打印結果:
FJChatMessageViewDemo[37740:787636] dealloc method
FJChatMessageViewDemo[37740:787636] 延遲 回調
六.最后:
送上一張喜歡的圖片:
如果覺得不錯冠骄,麻煩給個喜歡或star,如果有問題請及時反饋伪煤,謝謝!