ARC下的内存泄漏
##ARC下的內(nèi)存泄漏
ARC全稱叫 ARC(Automatic Reference Counting)。在編譯期間,編譯器會(huì)判斷對(duì)象的使用情況,并適當(dāng)?shù)募由蟫etain和release,使得對(duì)象的內(nèi)存被合理的管理。所以,從本質(zhì)上說(shuō)ARC和MRC在本質(zhì)上是一樣的,都是通過(guò)引用計(jì)數(shù)的內(nèi)存管理方式。ARC 的出現(xiàn)大大節(jié)省了程序員手動(dòng)管理內(nèi)存的時(shí)間成本,But,世上沒(méi)有完美的事物,我們也不要把任何事想的那么美好,在 ARC 環(huán)境下如果不注意的話也會(huì)引起內(nèi)存泄漏。
目前在項(xiàng)目中引入了MLeaksFinder,能比較清晰的找到內(nèi)存泄漏的位置。
##分析一下內(nèi)存泄漏的主要原因
####循環(huán)引用(Retain Cycle)
什么是引用循環(huán)(retain cycle) ?假設(shè)我們有兩個(gè)實(shí)例A和B,B是A的一個(gè)strong型的property,則B的引用計(jì)數(shù)是1,當(dāng)A的需要釋放的時(shí)候,A則會(huì)調(diào)用[B release]來(lái)釋放B,B的引用計(jì)數(shù)則減為0,釋放。
?可如果這時(shí)候?qū)的一個(gè)strong型property指向A,則A與B互相為強(qiáng)引用,問(wèn)題就來(lái)了。因?yàn)锽強(qiáng)引用A,A的引用計(jì)數(shù)永遠(yuǎn)不會(huì)減為0,當(dāng)A原本的強(qiáng)引用對(duì)象被釋放以后,A和B成為了一個(gè)相互引用的孤島,永遠(yuǎn)不會(huì)被釋放了,這就會(huì)引起內(nèi)存泄漏。
?在上面的例子中,就是一種非常普遍的引用循環(huán)情況,加入如上代碼的VC在dismiss或者pop以后,并不會(huì)執(zhí)行dealloc方法,證明內(nèi)存泄漏了。而引起泄漏的原因就是在作為self的property的block中,使用self指針導(dǎo)致self被block強(qiáng)引用,形成引用循環(huán)。
1、Delegate 我們?cè)谑褂么碓O(shè)計(jì)模式的時(shí)候,一定要注意將 delegate 變量聲明為 weak 類型,像這樣 @property (nonatomic, weak) id<xxxx> delegate; 如使用strong或別的類型修飾的話將會(huì)導(dǎo)致循環(huán)引用,導(dǎo)致dealloc()不會(huì)被調(diào)用。從而觸發(fā)一些意想不到的后果。
2、Block 目前在項(xiàng)目中出現(xiàn)的內(nèi)存泄漏大部分是因?yàn)閎lock的問(wèn)題。 在 ARC 下,當(dāng) block 獲取到外部變量時(shí),由于編譯器無(wú)法預(yù)測(cè)獲取到的變量何時(shí)會(huì)被突然釋放,為了保證程序能夠正確運(yùn)行,讓 block 持有獲取到的變量,向系統(tǒng)聲明:我要用它,你們千萬(wàn)別把它回收了!然而,也正因 block 持有了變量,容易導(dǎo)致變量和 block 的循環(huán)引用,造成內(nèi)存泄露!
? ? [_sortButton setButtonSpreadPreAction:^BOOL{if (_resultItems.count == 0) {[progressHUD showText:@"xxxx"];return NO;}return YES;}]; 復(fù)制代碼這個(gè)例子的問(wèn)題就在于在使用 block 的過(guò)程中形成了循環(huán)引用:self 持有 sortButton;sortButton 持有 block;block 持有 self。三者形成循環(huán)引用,內(nèi)存泄露。
GCD已經(jīng)一些系統(tǒng)級(jí)的API并不會(huì)提示循環(huán)引用的警告,但通過(guò)測(cè)試發(fā)現(xiàn),大部分系統(tǒng)提供block也是需要弱引用的__weak typeof(self) weakSelf = self; 項(xiàng)目中除了AFN的第三方組件在調(diào)用block時(shí)都是需要弱引用的,如MJRefresh。
3、NSTimer ?NSTimer在VC釋放前,一定要調(diào)用[timer invalidate],不調(diào)用的后果就是NSTimer無(wú)法釋放其target,如果target正好是self,則會(huì)導(dǎo)致引用循環(huán)。
?這里要補(bǔ)充一點(diǎn),引用循環(huán)不是只能有兩個(gè)對(duì)象,三個(gè)四個(gè)更多都是可以的,甚至環(huán)數(shù)也不一定只有一個(gè),所以要養(yǎng)成良好的代碼習(xí)慣,在NSTimer停用前調(diào)用invalidate方法。
關(guān)于performSelector:afterDelay的問(wèn)題
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay 復(fù)制代碼我們還是看看官方文檔怎么說(shuō)的。 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)依靠一個(gè)timer來(lái)保證延時(shí)觸發(fā),但是只有在runloop在default mode的時(shí)候才會(huì)執(zhí)行成功,否則selector會(huì)一直等待run loop切換到default mode。根據(jù)我們之前關(guān)于timer 的說(shuō)法,在這里其實(shí)調(diào)用performSelector:afterDelay:同樣會(huì)造成系統(tǒng)對(duì)target強(qiáng)引用,也即retain住。這樣子,如果selector一直無(wú)法執(zhí)行的話(比如runloop不是運(yùn)行在default model下),這樣子同樣會(huì)造成target一直無(wú)法被釋放掉,發(fā)生內(nèi)存泄露。怎么解決這個(gè)問(wèn)題呢?其實(shí)很簡(jiǎn)單,我們?cè)谶m當(dāng)?shù)臅r(shí)候取消掉該調(diào)用就行了,系統(tǒng)提供了接口:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget 復(fù)制代碼總結(jié)
- 上一篇: Linux 命令 之查看程序占用内存
- 下一篇: 梦到和老公要离婚了是什么征兆