iOS項目中常見的內(nèi)存泄漏場景

1. 計時器NSTimer

一方面timer作為VC的屬性,被VC強引用,創(chuàng)建timer對象時VC作為target被timer強引用,即循環(huán)引用。事實上這個說法是錯的。
及時timer不做為A的屬性,或者timer 的target 傳入__ weak, A也不會釋放。

網(wǎng)上的80%帖子都說 vc->time->target(vc) 實際是錯的 。
主要問題runloop對timer的引用,timer對持有對象的“強制性”強引用,
timer內(nèi)部會強引用target,即使傳進來的是weak類型,造成這個問題其實是:runLoop=>timer=>target才會造成內(nèi)存泄漏。

不準(zhǔn)時

1.第一種不準(zhǔn)時:有可能跳過去

線程處理比耗時的事情時會發(fā)生
還有就是timer添加到的runloop模式不是runloop當(dāng)前運行的模式,這種情況經(jīng)常發(fā)生。

2.并不會在準(zhǔn)確的時間觸發(fā),而是會延遲個很小的時間,原因也可以歸結(jié)為2點:

RunLoop為了節(jié)省資源,并不會在非常準(zhǔn)確的時間點觸發(fā)
線程有耗時操作,或者其它線程有耗時操作也會影響

iOS7以后,Timer 有個屬性叫做【 Tolerance】 (時間寬容度,默認(rèn)是0),標(biāo)示了當(dāng)時間點到后,容許有多少最大誤差。
它只會在準(zhǔn)確的觸發(fā)時間到加上Tolerance時間內(nèi)觸發(fā),而不會提前觸發(fā)(是不是有點像我們的火車,只會晚點。。。)。另外可重復(fù)定時器的觸發(fā)時間點不受Tolerance影響

解決Timer導(dǎo)致的循環(huán)引用

(1)運用 didMoveToParentViewController 方法 //當(dāng)前VC出棧調(diào)用
if paren == nil self.time invalidate selg.time = nil;

(2) 中間者模式 self.time ->_target
引用邏輯:self強引用timer強引用target
解決方法:
新建一個 target NSObject
運用 runtime class_addMethod [ _target class]
然后再 delloc 把定時器 失效

借助runtime給對象添加消息處理的能力
self強引用timer強引用target

_target = [[NSObject alloc] init];
    class_addMethod([_target class], @selector(fire), class_getMethodImplementation([self class], @selector(fire)), "v@:");
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(fire) userInfo:nil repeats:YES];

(3)第三種方案 NSProxy
引用邏輯:self強引用timer強引用proxy弱引用self
解決方法:
新建一個 NSProxy 中申明一個 weak 的target
然后用消息轉(zhuǎn)發(fā)機制

屏幕快照 2020-03-18 下午8.28.01.png
屏幕快照 2020-03-18 下午8.30.44.png

(4)NSTimer提供的API
引用邏輯:self強引用timer弱引用target
解決方法:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf fire];
}];

2.block導(dǎo)致的循環(huán)引用

block在copy時都會對block內(nèi)部用到的對象進行強引用(ARC)或者retainCount增1(非ARC)。在ARC與非ARC環(huán)境下對block使用不當(dāng)都會引起循環(huán)引用問題,一般表現(xiàn)為,某個類將block作為自己的屬性變量,然后該類在block的方法體里面又使用了該類本身,簡單說就是

self.someBlock = ^(Type var){
    [self dosomething];
}

這種循環(huán)引用會被編譯器捕捉到并及時提醒。

        self.block = ^(NSString *name){
            NSLog(@"arr:%@", self.arr);
        };

網(wǎng)上大部分帖子都表述為"block里面引用了self導(dǎo)致循環(huán)引用",但事實真的是如此嗎?我表示懷疑,其實這種說法是不嚴(yán)謹(jǐn)?shù)?,不一定要顯式地出現(xiàn)"self"字眼才會引起循環(huán)引用。我們改一下代碼,不通過屬性self.arr去訪問arr變量,而是通過實例變量_arr去訪問,如下:

由此我們知道了,即使在你的block代碼中沒有顯式地出現(xiàn)"self",也會出現(xiàn)循環(huán)引用!只要你在block里用到了 self所擁有的東西!
但對于這種情況,我們無法通過加__weak聲明或者_(dá)_block聲明去禁止block對self進行強引用或者強制增加引用計數(shù)。但我們可以通過其他指針來避免循環(huán)引用具體是這么做的:

__weak typeof(self) weakSelf = self;
self.blkA = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//加一下強引用,避免weakSelf被釋放掉
NSLog(@"%@", strongSelf->_xxView); //不會導(dǎo)致循環(huán)引用.
};

3. 代理 weak引用

4.wkWebView中 WKWebView在與js交互約定方法時

addScriptMessageHandler 導(dǎo)致循環(huán)引用

[configuration.userContentController addScriptMessageHandler:self name:name];
這里userContentController持有了self ,然后 userContentController 又被configuration持有,最終被wkwebview持有,然后wkwebview是self的一個成員變量,所以self也持有self,所以就造成了循環(huán)引用,導(dǎo)致界面不會被釋放

解決方案:

viewWillAppear里面調(diào)用addScriptMessageHandler
viewWillDisappear 調(diào)用removeScriptMessageHandlerForName

5.performSelector內(nèi)存泄露
performSelector延時調(diào)用的問題,
performSelector關(guān)于內(nèi)存管理的執(zhí)行原理是這樣的:執(zhí)行 [self performSelector:@selector(method1:) withObject:self afterDelay:3]; 的時候,系統(tǒng)會將self的引用計數(shù)加1,執(zhí)行完這個方法時,還會將self的引用計數(shù)減1,當(dāng)方法還沒有執(zhí)行的時候,要返回父視圖釋放當(dāng)前視圖的時候,self的計數(shù)沒有減少到0,而導(dǎo)致無法調(diào)用dealloc方法,出現(xiàn)了內(nèi)存泄露。

所以最后我的解決辦法就是取消那些還沒有來得及執(zhí)行的延時函數(shù),代碼很簡單:

[NSObject cancelPreviousPerformRequestsWithTarget:self]

當(dāng)然你也可以一個一個得這樣用:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(method1:) object:nil]
加上了這個以后,dealloc方法就會被調(diào)用,問題解決!

6.CF類型內(nèi)存

注意以creat,copy作為關(guān)鍵字的函數(shù)都是需要釋放內(nèi)存的,注意配對使用。比如:CGColorCreate<-->CGColorRelease
class_copyPropertyList 【 free】 釋放

7.AFNetWorking 3.0

原因是manager類每一次網(wǎng)絡(luò)請求都是初始化一個實例對象,但是該對象在工程中得不到釋放,造成了內(nèi)存泄漏。我也不知道AFNetWorking的作者是怎么想的。解決方法是創(chuàng)建一個繼承與AFHTTPSessionManager 的單例對象,每次網(wǎng)絡(luò)請求都調(diào)用這個單例方法。

8.NSNotification

使用block的方式增加notification,引用了self,在刪除notification之前,self不會被釋放,與timer的場景類似,其實這段代碼已經(jīng)聲明了weakself,但是調(diào)用_eventManger方法還是引起了循環(huán)引用。
也就是說,即使我們沒有調(diào)用self方法,_xxx也會造成循環(huán)引用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 歡迎訪問我的博客原文 內(nèi)存泄漏指的是程序中已動態(tài)分配的堆內(nèi)存(程序員自己管理的空間)由于某些原因未能釋放或無法釋放...
    FiTeen閱讀 2,783評論 1 5
  • 相關(guān)文章: 來自網(wǎng)絡(luò),原文鏈接 http://www.cocoachina.com/ios/20150825/13...
    小楓123閱讀 846評論 0 2
  • 摘自: http://www.cocoachina.com/ios/20150825/13195.html 25...
    program袁閱讀 1,524評論 0 1
  • 定時器的用法 系統(tǒng)提供了8個創(chuàng)建方法,6個類創(chuàng)建方法,2個實例初始化方法。有三個方法直接將timer添加到...
    gpylove閱讀 1,886評論 1 3
  • 在愛的辛福國度,你就是我唯一,我唯一愛的就是你。不知道為什么,腦子里就是單曲循環(huán)這首歌
    喬喬的一天閱讀 127評論 0 0

友情鏈接更多精彩內(nèi)容