級別: ★★☆☆☆
標籤:「iOS」「記憶體洩漏排查」「Leaks工具」
作者: MrLiuQ
審校: QiShare團隊
本文將從以下兩個層面解決iOS記憶體洩漏問題:
- 記憶體洩漏排查方法(工具)
- 記憶體洩漏原因分析(解決方案)
在正式開始前,我們先區分兩個基本概念:
- 記憶體洩漏(memory leak):是指申請的記憶體空間使用完畢之後未回收。 一次記憶體洩露危害可以忽略,但若一直洩漏,無論有多少記憶體,遲早都會被佔用光,最終導致程式
crash
。(因此,開發中我們要儘量避免記憶體洩漏的出現)- 記憶體溢位(out of memory):是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用。 通俗理解就是記憶體不夠用了,通常在執行大型應用或遊戲時,應用或遊戲所需要的記憶體遠遠超出了你主機內安裝的記憶體所承受大小,就叫記憶體溢位。最終導致機器
重啟
或者程式crash
。
簡單來說:
概念 | 區別說明 |
---|---|
記憶體洩漏 | 供應方(作業系統)能提供給需求方(App)的記憶體越來越少。 |
記憶體溢位 | 需求方(App)需要的記憶體過大,超過供應方(作業系統)負載。 |
一、排查方法
我們知道,iOS開發有“ARC機制”幫忙管理記憶體,但在實際開發中,如果處理不好堆空間上的記憶體還是會存在記憶體洩漏的問題。如果記憶體洩漏嚴重,最終會導致程式的崩潰。
首先,我們需要檢查我們的App有沒有記憶體洩漏,並且快速定位到記憶體洩漏的程式碼。目前比較常用的記憶體洩漏的排查方法有兩種,都在Xcode中可以直接使用:
- 第一種:靜態分析方法(
Analyze
) - 第二種:動態分析方法(
Instrument
工具庫裡的Leaks
)。一般推薦使用第二種。
1.1 靜態記憶體洩漏分析方法:
-
第一步:通過Xcode開啟專案,然後點選Product->Analyze,開始進入靜態記憶體洩漏分析。 如下圖所示:
-
第二步:等待分析結果。
-
第三步:根據分析的結果對可能造成記憶體洩漏的程式碼進行排查,如下圖所示。
PS:靜態記憶體洩漏分析能發現大部分問題,但只是靜態分析,並且並不準確,只是有可能發生記憶體洩漏。一些動態記憶體分配的情形並沒有分析。如果需要更精準一些,那就要用到下面要介紹的動態記憶體洩漏分析方法(Instruments工具中的
Leaks
方法)進行排查。
1.2 動態記憶體洩漏分析方法:
靜態記憶體洩漏分析不能把所有的記憶體洩漏排查出來,因為有的記憶體洩漏發生在執行時,當使用者做某些操作時才發生記憶體洩漏。這是就要使用動態記憶體洩漏檢測方法了。
步驟如下:
- 第一步:通過Xcode開啟專案,然後點選Product->Profile,如下圖所示:
- 第二步:按上面操作,build成功後跳出Instruments工具,如上圖右側圖所示。選擇
Leaks
選項,點選右下角的【choose】按鈕。如下圖:
- 第三步:這時候專案程式也在模擬器或手機上執行起來了,在手機或模擬器上對程式進行操作,工具顯示效果如下:
點選左上角的紅色圓點,這時專案開始啟動了,由於Leaks
是動態監測,所以手動進行一系列操作,可檢查專案中是否存在記憶體洩漏問題。如圖所示,橙色矩形框中所示綠色為正常,如果出現如右側紅色矩形框中顯示紅色,則表示出現記憶體洩漏。
選中Leaks Checks,在Details所在欄中選擇CallTree,並且在右下角勾選Invert Call Tree
和Hide System Libraries
,會發現顯示若干行程式碼,雙擊即可跳轉到出現記憶體洩漏的地方,修改即可。
舉個例子:
PS:AFHTTPSessionManager記憶體洩漏是一個很常見的問題:解決方法有兩種:點選這裡
二、記憶體洩漏的原因分析
目前,在ARC環境下,導致記憶體洩漏的根本原因是程式碼中存在迴圈引用,從而導致一些記憶體無法釋放,最終導致dealloc()方法無法被呼叫。主要原因大概有一下幾種型別:
2.1 ViewController中存在NSTimer
如果你的ViewController中有NSTimer,那麼你就要注意了,因為當你呼叫
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(updateTime:)
userInfo:nil
repeats:YES];
複製程式碼
- 理由:這時
target: self
,增加了ViewController的retain count
, 即self
強引用timer
,timer
強引用self
。造成迴圈引用。 - 解決方案:在恰當時機呼叫
[timer invalidate]
即可。
2.2 ViewController中的代理delegate
代理在一般情況下,需要使用weak修飾。如果你這個VC需要外部傳某個delegate進來,通過delegate+protocol的方式傳引數給其他物件,那麼這個delegate一定不要強引用,儘量使用weak修飾,否則你的VC會持續持有這個delegate,直到代理自身被釋放。
- 理由:如果代理用
strong
修飾,ViewController(self
)會強引用View
,View
強引用delegate
,delegate
內部強引用ViewController(self
)。造成記憶體洩漏。 - 解決方案:代理儘量使用
weak
修飾。
舉個例子:代理一般用weak
修飾,避免迴圈引用。
@class QiAnimationButton;
@protocol QiAnimationButtonDelegate <NSObject>
@optional
- (void)animationButton:(QiAnimationButton *)button willStartAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button didStartAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button willStopAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button didStopAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button didRevisedAnimationWithCircleView:(QiCircleAnimationView *)circleView;
@end
@interface QiAnimationButton : UIButton
@property (nonatomic, weak) id <QiAnimationButtonDelegate> delegate;
- (void)startAnimation; //!< 開始動畫
- (void)stopAnimation; //!< 結束動畫
- (void)reverseAnimation; //!< 最後的修改動畫
複製程式碼
2.3 ViewController中Block
在我們日常開發中,如果block使用不當,很容易導致記憶體洩漏。
- 理由:如果
block
被當前ViewController(self
)持有,這時,如果block內部再持有ViewController(self
),就會造成迴圈引用。 - 解決方案:在
block
外部對弱化self
,再在block內部強化已經弱化的weakSelf
For Example:
__weak typeof(self) weakSelf = self;
[self.operationQueue addOperationWithBlock:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (completionHandler) {
KTVHCLogDataStorage(@"serial reader async end, %@", request.URLString);
completionHandler([strongSelf serialReaderWithRequest:request]);
}
}];
複製程式碼
關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)
推薦文章:
Web安全漏洞之CSRF
2018蘋果秋季新品釋出會速覽