</br>
前言
內(nèi)存泄露是一個(gè)相對(duì)挺嚴(yán)重的問題,可是它的存在未引起足夠的重視,如果程序運(yùn)行時(shí)一直分配內(nèi)存而不及時(shí)釋放無用的內(nèi)存,程序占用的內(nèi)存越來越大,直到把系統(tǒng)分配給該APP的內(nèi)存消耗殫盡洽糟,程序因無內(nèi)存可用導(dǎo)致崩潰,這樣的情況我們稱之為內(nèi)存泄漏堕战。如果某個(gè)對(duì)象沒有始終在內(nèi)存中坤溃,并且依然會(huì)做一些事的時(shí)候,這樣的的Bug是非常嚴(yán)重而且難以排查的嘱丢。
內(nèi)存泄漏可能引起的問題:
- 內(nèi)存消耗殆盡的時(shí)候薪介,程序會(huì)因沒有內(nèi)存被殺死,即crash屿讽。
- 當(dāng)內(nèi)存快要用完的時(shí)候昭灵,會(huì)非常的卡頓
- 如果是ViewController沒有釋放掉,引起的內(nèi)存泄露伐谈,還會(huì)引起其他嚴(yán)重的問題烂完,尤其是和通知相關(guān)的。沒有被釋放掉的ViewController還能接收通知诵棵,還會(huì)執(zhí)行相關(guān)的動(dòng)作抠蚣,所以會(huì)引起各種各樣的異常情況的發(fā)生。
那么ARC下內(nèi)存泄漏的場景有哪些呢
值得注意的是:ARC是編譯器(時(shí))特性履澳,而不是運(yùn)行時(shí)特性嘶窄,更不是垃圾回收器(GC)。
ARC這是一種編譯期的內(nèi)存管理方式距贷,在編譯期間柄冲,編譯器會(huì)判斷對(duì)象的使用情況,并在合適的位置加上retain和release忠蝗,使得對(duì)象的內(nèi)存被合理的管理现横。所以,從本質(zhì)上說ARC和MRC在本質(zhì)上是一樣的,都是通過引用計(jì)數(shù)的內(nèi)存管理方式戒祠。
- CF類型內(nèi)存
ARC 可以幫忙管理 Objective-C 對(duì)象, 但是不支持 Core Foundation 對(duì)象的管理骇两,所以轉(zhuǎn)換后要注意一個(gè)問題:誰來釋放使用后的對(duì)象。
注意以creat,copy作為關(guān)鍵字的函數(shù)都是需要釋放內(nèi)存的姜盈,注意配對(duì)使用低千。比如:CGColorCreate<-->CGColorRelease
那Objective-C 和 Core Foundation 對(duì)象相互轉(zhuǎn)換時(shí)就可能出現(xiàn)內(nèi)存泄漏的問題,可參考這篇文章處理馏颂。
-
MRC內(nèi)存使用
這部分不做詳細(xì)介紹示血,也是注意配對(duì)使用,需要說明的是救拉,如果代碼中有部分文件是MRC的矾芙,在已有文件中加代碼的時(shí)候注意一下,不能都按照ARC的方式處理近上。
-
循環(huán)引用
-
block引起的循環(huán)引用。
某個(gè)類將block作為自己的屬性變量拂铡,然后該類在block的方法體里面又使用了該類本身;相互持有壹无,導(dǎo)致都釋放不了。
下面這樣的方式就可以解決block引起的循環(huán)引用:
__weaktypeof(self) weakSelf =self;
block內(nèi)的self感帅,換成weakSelf就行了斗锭。
block不是self的屬性或者變量時(shí),在block內(nèi)使用self不會(huì)循環(huán)引用;
像這樣的方法中調(diào)用self失球,不會(huì)引起岖是,但是屬性的形式中調(diào)用self就會(huì)以[self.myTest doSomeTest:^(NSInteger cellIndex) { self.allInter = cellIndex; }];
-
引用大循環(huán)
?就像前面說的,引用循環(huán)可能是一個(gè)大循環(huán)实苞。我遇到過一種情況豺撑,就是給UITableViewCell設(shè)置block屬性響應(yīng)事件,在block中強(qiáng)引用了self黔牵,
導(dǎo)致self->tableView->cell->self形成循環(huán)聪轿。
有時(shí)候隨著代碼量的增大,邏輯的負(fù)責(zé)猾浦,很容易形成一個(gè)很大的循環(huán)引用陆错,最后造成內(nèi)存泄漏。-
** NSTimer的使用**
NSTimer金赦,NSTimer會(huì)對(duì)它的target持有強(qiáng)引用音瓷,如果NSTimer不釋放掉,就會(huì)一直持有它的target的強(qiáng)引用夹抗,如果這個(gè)NSTimer在被target強(qiáng)引用绳慎,會(huì)一直都釋放不掉,造成內(nèi)存泄露。
下面的代碼在書寫的時(shí)候Xcode是不會(huì)報(bào)任何錯(cuò)誤和警告的偷线。但是實(shí)際上已經(jīng)形成了循環(huán)引用磨确。造成了內(nèi)存泄漏。
@property (nonatomic, strong) NSTimer *timer; @property(copy,nonatomic)NSString *name; self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(handleTimer) userInfo:nil repeats:YES]; - (void)handleTimer { self.name = @"123"; }
-
單例也會(huì)造成內(nèi)存泄漏
如果一個(gè)單例持有一個(gè)block声邦,block內(nèi)又使用了當(dāng)前這個(gè)ViewController類乏奥,會(huì)引起循環(huán)引用。所以單例持有的代碼塊中要用弱引用亥曹,原因是:單例不會(huì)被釋放掉邓了,它會(huì)一直持有block,導(dǎo)致該block所在的ViewController釋放不掉媳瞪。
-
performSelector的內(nèi)存問題
-
performSelector 的動(dòng)態(tài)綁定
SEL selector; if (/* some condition */) { selector = @selector(newObject); } else if (/* some other condition */) { selector = @selector(copy); } else { selector = @selector(someProperty); } id ret = [object performSelector:selector];
這段代碼就相當(dāng)于在動(dòng)態(tài)之上再動(dòng)態(tài)綁定骗炉。在 ARC 下編譯這段代碼,編譯器會(huì)發(fā)出警告
warning: performSelector may cause a leak because its selector is unknow [-Warc-performSelector-leak]
正是由于動(dòng)態(tài)蛇受,編譯器不知道即將調(diào)用的 selector 是什么句葵,不了解方法簽名和返回值,甚至是否有返回值都不懂兢仰,所以編譯器無法用 ARC 的內(nèi)存管理規(guī)則來判斷返回值是否應(yīng)該釋放乍丈。因此,ARC 采用了比較謹(jǐn)慎的做法把将,不添加釋放操作轻专,即在方法返回對(duì)象的引用計(jì)數(shù)可能不會(huì)減少,從而可能導(dǎo)致內(nèi)存泄露察蹲。
以本段代碼為例请垛,前兩種情況(newObject, copy)都需要再次釋放,而第三種情況不需要洽议。這種泄露隱藏得如此之深宗收,以至于使用 static analyzer 都很難檢測到。如果把代碼的最后一行改成
[object performSelector:selector];
不創(chuàng)建一個(gè)返回值變量測試分析亚兄,簡直難以想象這里居然會(huì)出現(xiàn)內(nèi)存問題镜雨。所以如果你使用的 selector 有返回值,一定要處理掉 手動(dòng)釋放(置為 nil)儿捧。
-
performSelector afterDelay 延時(shí)操作
關(guān)于內(nèi)存管理的執(zhí)行原理是這樣的執(zhí)行
[self performSelector:@selector(method1:) withObject:self.tableLayer afterDelay:3];
的時(shí)候荚坞,系統(tǒng)會(huì)將tableLayer的引用計(jì)數(shù)加1,執(zhí)行完這個(gè)方法時(shí)菲盾,還會(huì)將tableLayer的引用計(jì)數(shù)減1颓影,有時(shí)切換場景時(shí)延時(shí)函數(shù)已經(jīng)被調(diào)用但還沒有執(zhí)行,這時(shí)tableLayer的引用計(jì)數(shù)并沒有減少到0懒鉴,也就導(dǎo)致了切換場景dealloc方法沒有被調(diào)用诡挂,出現(xiàn)了內(nèi)存泄露碎浇。解決辦法就是取消那些還沒有來得及執(zhí)行的延時(shí)函數(shù),代碼:
[NSObject cancelPreviousPerformRequestsWithTarget:self]
當(dāng)然你也可以一個(gè)一個(gè)得這樣用:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(method1:) object:nil]
加上了這個(gè)以后璃俗,切換場景后會(huì)順利地執(zhí)行了dealloc方法奴璃,至此內(nèi)存泄漏問題解決。
-
-
代理未清空引起野指針
查看iOS的一些API城豁,發(fā)現(xiàn)delegate都是assign的苟穆,這樣就會(huì)引起野指針的問題,可能會(huì)引起一些莫名其妙的crash唱星。那么這是怎么引起的雳旅,當(dāng)一個(gè)對(duì)象被回收時(shí),對(duì)應(yīng)的delegate實(shí)體也就被回收间聊,但是delegate的指針確沒有被nil攒盈,從而就變成了游蕩的野指針了。所以在delloc方法中要將對(duì)應(yīng)的assign代理設(shè)置為nil哎榴,如:
- (void)viewDidDisappear:(BOOL)animate { self.myTableView.delegate = nil; self.myTableView.dataSource = nil; 通知注銷掉 kvo remove掉~~ }
那是不是所有的delegate都要這樣做呢型豁?一般自己寫的一些delegate,我們會(huì)用weak尚蝌,而不是assign偷遗,weak的好處是當(dāng)對(duì)應(yīng)的對(duì)象被回收時(shí),指針也會(huì)自動(dòng)被設(shè)置為nil驼壶。
-
循環(huán)未結(jié)束
如果某個(gè)ViewController中有無限循環(huán),也會(huì)導(dǎo)致即使ViewController對(duì)應(yīng)的view關(guān)掉了喉酌,ViewController也不能被釋放热凹。
這種問題常發(fā)生于animation處理。CATransition *transition = [CATransition animation]; transition.duration = 0.5; tansition.repeatCount = HUGE_VALL; [self.view.layer addAnimation:transition forKey:"myAnimation"];
上例中泪电,animation重復(fù)次數(shù)設(shè)成HUGE_VALL般妙,一個(gè)很大的數(shù)值,基本上等于無限循環(huán)了相速。
解決辦法是碟渺,在ViewController關(guān)掉的時(shí)候,停止這個(gè)animation突诬。
-(void)viewWillDisappear:(BOOL)animated {
[self.view.layer removeAllAnimations];
}
- ** try...catch 的使用**
但如果 doSomethingMayThrowException 方法拋出了異常苫拍,那么 object 對(duì)象就無法釋放。如果 object 對(duì)象持有了重要且稀缺的資源旺隙,就可能會(huì)造成嚴(yán)重后果绒极。
PS其他需要注意的問題
大次數(shù)循環(huán)內(nèi)存暴漲問題
記得有道比較經(jīng)典的面試題,查看如下代碼有何問題:
for (int i = 0; i < 100000; i++) {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
該循環(huán)內(nèi)產(chǎn)生大量的臨時(shí)對(duì)象蔬捷,直至循環(huán)結(jié)束才釋放垄提,可能導(dǎo)致內(nèi)存泄漏榔袋,解決方法為在循環(huán)中創(chuàng)建自己的autoReleasePool,及時(shí)釋放占用內(nèi)存大的臨時(shí)變量铡俐,減少內(nèi)存占用峰值凰兑。
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
}
附、如何檢測App的內(nèi)存泄漏問題
-
借助Xcode自帶的Instruments工具(選取真機(jī)測試)
-
簡單暴力的重寫dealloc方法审丘,加入斷點(diǎn)或打印判斷某類是否正常釋放吏够。
-
使用Xcode8中自帶的有內(nèi)存檢測警告。
通過Facebook出品的FBMemoryProfiler工具類進(jìn)行檢測.
</br>
這篇ARC下的內(nèi)存泄漏备恤,洋洋灑灑說了這么多稿饰,算是總結(jié)的比較詳細(xì)和全面的。希望對(duì)大家有價(jià)值露泊。