循環(huán)引用(Retain Cycle)
先簡單說一下什么是循環(huán)引用(retain cycle)
?假設(shè)我們有兩個實例A和B甫窟,B是A的一個strong型的property径密,則B的引用計數(shù)是1,當(dāng)A的需要釋放的時候,A則會調(diào)用[B release]來釋放B,B的引用計數(shù)則減為0快毛,釋放。
?可如果這時候?qū)的一個strong型property指向A番挺,則A與B互相為強引用唠帝,問題就來了。因為B強引用A建芙,A的引用計數(shù)永遠(yuǎn)不會減為0没隘,當(dāng)A原本的強引用對象被釋放以后,A和B成為了一個相互引用的孤島禁荸,永遠(yuǎn)不會被釋放了右蒲,這就會引起內(nèi)存泄漏。
?在上面的例子中赶熟,就是一種非常普遍的引用循環(huán)情況瑰妄,加入如上代碼的VC在dismiss或者pop以后,并不會執(zhí)行dealloc方法映砖,證明內(nèi)存泄漏了间坐。而引起泄漏的原因就是在作為self的property的block中,使用self指針導(dǎo)致self被block強引用,形成引用循環(huán)竹宋。
總結(jié)一下出現(xiàn)內(nèi)存泄漏的幾種常見情況
1劳澄、Delegate/NSNotification
我們在使用代理設(shè)計模式的時候,一定要注意將 delegate 變量聲明為 weak 類型蜈七,像這樣
如使用strong或別的類型修飾的話將會導(dǎo)致循環(huán)引用秒拔,導(dǎo)致dealloc()不會被調(diào)用。NSNotification沒有移除通知等都會觸發(fā)一些意想不到的后果飒硅。
2砂缩、Block
目前在項目中出現(xiàn)的內(nèi)存泄漏大部分是因為block的問題。
在 ARC 下三娩,當(dāng) block 獲取到外部變量時庵芭,由于編譯器無法預(yù)測獲取到的變量何時會被突然釋放,為了保證程序能夠正確運行雀监,讓 block 持有獲取到的變量双吆,向系統(tǒng)聲明:我要用它,你們千萬別把它回收了滔悉!然而伊诵,也正因 block 持有了變量单绑,容易導(dǎo)致變量和 block 的循環(huán)引用回官,造成內(nèi)存泄露
[_sortButton setButtonSpreadPreAction:^BOOL{
if (_resultItems.count == 0) {
[progressHUD showText:@"xxxx"];
return NO;
}
return YES;
}];
這個例子的問題就在于在使用 block 的過程中形成了循環(huán)引用:self 持有 sortButton;sortButton 持有 block搂橙;block 持有 self歉提。三者形成循環(huán)引用,內(nèi)存泄露区转。
GCD已經(jīng)一些系統(tǒng)級的API并不會提示循環(huán)引用的警告苔巨,但通過測試發(fā)現(xiàn),大部分系統(tǒng)提供block也是需要弱引用的__weak typeof(self) weakSelf = self;
項目中除了AFN的第三方組件在調(diào)用block時都是需要弱引用的废离。
3侄泽、NSTimer
?NSTimer在釋放前,一定要調(diào)用[timer invalidate]蜻韭,不調(diào)用的后果就是NSTimer無法釋放其target悼尾,如果target正好是self,則會導(dǎo)致引用循環(huán)肖方。
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
官方文檔是這樣說的:
This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.
大概意思是系統(tǒng)依靠一個timer來保證延時觸發(fā)闺魏,但是只有在runloop在default mode的時候才會執(zhí)行成功,否則selector會一直等待run loop切換到default mode俯画。根據(jù)我們之前關(guān)于timer
的說法析桥,在這里其實調(diào)用performSelector:afterDelay:同樣會造成系統(tǒng)對target強引用,也即retain住。這樣子泡仗,如果selector一直無法執(zhí)行的話(比如runloop不是運行在default model下),這樣子同樣會造成target一直無法被釋放掉埋虹,發(fā)生內(nèi)存泄露。怎么解決這個問題呢娩怎?其實很簡單吨岭,我們在適當(dāng)?shù)臅r候取消掉該調(diào)用就行了,系統(tǒng)提供了接口:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget
?這里要補充一點峦树,引用循環(huán)不是只能有兩個對象辣辫,三個四個更多都是可以的,甚至環(huán)數(shù)也不一定只有一個魁巩,所以要養(yǎng)成良好的代碼習(xí)慣急灭,在NSTimer停用前調(diào)用invalidate方法。
4谷遂、Image內(nèi)存過大
5葬馋、Foundation與CoreFoundation的相互引用也會造成內(nèi)存泄漏
6.AFN 的NSURLSession不能釋放
//解決辦法:
//修改AFHTTPSessionManager 的manager方法,替換manager;
//或繼承其肾扰,自己寫個manager方法
//另一種寫法畴嘶,兩個單例:
+ (AFHTTPSessionManager *)sharedHTTPSession{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = 30;
[manager.requestSerializer setValue:@"XMLHttpRequest"
forHTTPHeaderField:@"X-Requested-With"];
});
return manager;
}
+ (AFURLSessionManager *)sharedURLSession{
static dispatch_once_t onceToken2;
dispatch_once(&onceToken2, ^{
urlsession = [[AFURLSessionManager alloc] initWithSessionConfiguration:
[NSURLSessionConfiguration defaultSessionConfiguration]];
});
return urlsession;
}
7.UIWebView 不能釋放
目前只能用WKWebView 了。其他方法試了下集晚,都沒什么用
WKWebview也會造成內(nèi)存泄漏窗悯,在OC與JS互相調(diào)用時,
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
如果不remove掉會使得此VC不走dealloc
感謝微辣小龍蝦的指點