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ā)機制


(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)引用。