</br>
前言
內存泄露是一個相對挺嚴重的問題,可是它的存在未引起足夠的重視,如果程序運行時一直分配內存而不及時釋放無用的內存,程序占用的內存越來越大,直到把系統分配給該APP的內存消耗殫盡,程序因無內存可用導致崩潰,這樣的情況我們稱之為內存泄漏。如果某個對象沒有始終在內存中,并且依然會做一些事的時候,這樣的的Bug是非常嚴重而且難以排查的。
內存泄漏可能引起的問題:
- 內存消耗殆盡的時候,程序會因沒有內存被殺死,即crash。
- 當內存快要用完的時候,會非常的卡頓
- 如果是ViewController沒有釋放掉,引起的內存泄露,還會引起其他嚴重的問題,尤其是和通知相關的。沒有被釋放掉的ViewController還能接收通知,還會執行相關的動作,所以會引起各種各樣的異常情況的發生。
那么ARC下內存泄漏的場景有哪些呢
值得注意的是:ARC是編譯器(時)特性,而不是運行時特性,更不是垃圾回收器(GC)。
ARC這是一種編譯期的內存管理方式,在編譯期間,編譯器會判斷對象的使用情況,并在合適的位置加上retain和release,使得對象的內存被合理的管理。所以,從本質上說ARC和MRC在本質上是一樣的,都是通過引用計數的內存管理方式。
- CF類型內存
ARC 可以幫忙管理 Objective-C 對象, 但是不支持 Core Foundation 對象的管理,所以轉換后要注意一個問題:誰來釋放使用后的對象。
注意以creat,copy作為關鍵字的函數都是需要釋放內存的,注意配對使用。比如:CGColorCreate<-->CGColorRelease
那Objective-C 和 Core Foundation 對象相互轉換時就可能出現內存泄漏的問題,可參考這篇文章處理。
-
MRC內存使用
這部分不做詳細介紹,也是注意配對使用,需要說明的是,如果代碼中有部分文件是MRC的,在已有文件中加代碼的時候注意一下,不能都按照ARC的方式處理。
-
循環引用
-
block引起的循環引用。
某個類將block作為自己的屬性變量,然后該類在block的方法體里面又使用了該類本身;相互持有,導致都釋放不了。
下面這樣的方式就可以解決block引起的循環引用:
__weaktypeof(self) weakSelf =self;
block內的self,換成weakSelf就行了。
block不是self的屬性或者變量時,在block內使用self不會循環引用;
像這樣的方法中調用self,不會引起,但是屬性的形式中調用self就會以[self.myTest doSomeTest:^(NSInteger cellIndex) { self.allInter = cellIndex; }];
-
引用大循環
?就像前面說的,引用循環可能是一個大循環。我遇到過一種情況,就是給UITableViewCell設置block屬性響應事件,在block中強引用了self,
導致self->tableView->cell->self形成循環。
有時候隨著代碼量的增大,邏輯的負責,很容易形成一個很大的循環引用,最后造成內存泄漏。-
** NSTimer的使用**
NSTimer,NSTimer會對它的target持有強引用,如果NSTimer不釋放掉,就會一直持有它的target的強引用,如果這個NSTimer在被target強引用,會一直都釋放不掉,造成內存泄露。
下面的代碼在書寫的時候Xcode是不會報任何錯誤和警告的。但是實際上已經形成了循環引用。造成了內存泄漏。
@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"; }
-
單例也會造成內存泄漏
如果一個單例持有一個block,block內又使用了當前這個ViewController類,會引起循環引用。所以單例持有的代碼塊中要用弱引用,原因是:單例不會被釋放掉,它會一直持有block,導致該block所在的ViewController釋放不掉。
-
performSelector的內存問題
-
performSelector 的動態綁定
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];
這段代碼就相當于在動態之上再動態綁定。在 ARC 下編譯這段代碼,編譯器會發出警告
warning: performSelector may cause a leak because its selector is unknow [-Warc-performSelector-leak]
正是由于動態,編譯器不知道即將調用的 selector 是什么,不了解方法簽名和返回值,甚至是否有返回值都不懂,所以編譯器無法用 ARC 的內存管理規則來判斷返回值是否應該釋放。因此,ARC 采用了比較謹慎的做法,不添加釋放操作,即在方法返回對象的引用計數可能不會減少,從而可能導致內存泄露。
以本段代碼為例,前兩種情況(newObject, copy)都需要再次釋放,而第三種情況不需要。這種泄露隱藏得如此之深,以至于使用 static analyzer 都很難檢測到。如果把代碼的最后一行改成
[object performSelector:selector];
不創建一個返回值變量測試分析,簡直難以想象這里居然會出現內存問題。所以如果你使用的 selector 有返回值,一定要處理掉 手動釋放(置為 nil)。
-
performSelector afterDelay 延時操作
關于內存管理的執行原理是這樣的執行
[self performSelector:@selector(method1:) withObject:self.tableLayer afterDelay:3];
的時候,系統會將tableLayer的引用計數加1,執行完這個方法時,還會將tableLayer的引用計數減1,有時切換場景時延時函數已經被調用但還沒有執行,這時tableLayer的引用計數并沒有減少到0,也就導致了切換場景dealloc方法沒有被調用,出現了內存泄露。解決辦法就是取消那些還沒有來得及執行的延時函數,代碼:
[NSObject cancelPreviousPerformRequestsWithTarget:self]
當然你也可以一個一個得這樣用:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(method1:) object:nil]
加上了這個以后,切換場景后會順利地執行了dealloc方法,至此內存泄漏問題解決。
-
-
代理未清空引起野指針
查看iOS的一些API,發現delegate都是assign的,這樣就會引起野指針的問題,可能會引起一些莫名其妙的crash。那么這是怎么引起的,當一個對象被回收時,對應的delegate實體也就被回收,但是delegate的指針確沒有被nil,從而就變成了游蕩的野指針了。所以在delloc方法中要將對應的assign代理設置為nil,如:
- (void)viewDidDisappear:(BOOL)animate { self.myTableView.delegate = nil; self.myTableView.dataSource = nil; 通知注銷掉 kvo remove掉~~ }
那是不是所有的delegate都要這樣做呢?一般自己寫的一些delegate,我們會用weak,而不是assign,weak的好處是當對應的對象被回收時,指針也會自動被設置為nil。
-
循環未結束
如果某個ViewController中有無限循環,也會導致即使ViewController對應的view關掉了,ViewController也不能被釋放。
這種問題常發生于animation處理。CATransition *transition = [CATransition animation]; transition.duration = 0.5; tansition.repeatCount = HUGE_VALL; [self.view.layer addAnimation:transition forKey:"myAnimation"];
上例中,animation重復次數設成HUGE_VALL,一個很大的數值,基本上等于無限循環了。
解決辦法是,在ViewController關掉的時候,停止這個animation。
-(void)viewWillDisappear:(BOOL)animated {
[self.view.layer removeAllAnimations];
}
- ** try...catch 的使用**
但如果 doSomethingMayThrowException 方法拋出了異常,那么 object 對象就無法釋放。如果 object 對象持有了重要且稀缺的資源,就可能會造成嚴重后果。
PS其他需要注意的問題
大次數循環內存暴漲問題
記得有道比較經典的面試題,查看如下代碼有何問題:
for (int i = 0; i < 100000; i++) {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
該循環內產生大量的臨時對象,直至循環結束才釋放,可能導致內存泄漏,解決方法為在循環中創建自己的autoReleasePool,及時釋放占用內存大的臨時變量,減少內存占用峰值。
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
}
附、如何檢測App的內存泄漏問題
-
借助Xcode自帶的Instruments工具(選取真機測試)
Instruments -
簡單暴力的重寫dealloc方法,加入斷點或打印判斷某類是否正常釋放。
dealloc -
使用Xcode8中自帶的有內存檢測警告。
通過Facebook出品的FBMemoryProfiler工具類進行檢測.
</br>
這篇ARC下的內存泄漏,洋洋灑灑說了這么多,算是總結的比較詳細和全面的。希望對大家有價值。