iOS - 內(nèi)存泄漏檢測方法

轉自:http://www.reibang.com/p/9fc2132d09c7

三方工具

什么是內(nèi)存泄漏掰读,通俗來說就是有一塊內(nèi)存區(qū)域被你占用了村生,但你又不使用這塊區(qū)域也不讓別人用拉讯,造成內(nèi)存浪費罢杉,這就是內(nèi)存泄漏齐邦,泄漏嚴重會造成內(nèi)存吃緊锰蓬,嚴重的會使程序崩潰锣险;
內(nèi)存泄漏對于以前MRC開發(fā)來說相當痛苦,需要耗費大量精力管理內(nèi)存栽渴,引入ARC機制后尖坤,系統(tǒng)自動管理內(nèi)存,大大減輕了開發(fā)工作量闲擦,但一些特殊情況仍然會有內(nèi)存泄漏發(fā)生慢味,需要特別注意。

一般易造成泄漏的點
  • Retain Cycle墅冷,Block強引用
  • NSTimer釋放不當
  • 第三方提供方法造成的內(nèi)存泄漏
  • CoreFoundation方式申請的內(nèi)存纯路,忘記釋放

eg:

block循環(huán)

 [cell setSelectTagCityBlock:^(NSIndexPath *indexPath, NSInteger index){
        [self tableView:_tableViewCityList didSelectRowAtIndexPath:indexPath index:index];
    }];

一般用weak打破保留環(huán)

@WeakObj(self)
    [cell setSelectTagCityBlock:^(NSIndexPath *indexPath, NSInteger index){
        if (selfWeak)
        {
            [selfWeak tableView:selfWeak.tableViewCityList didSelectRowAtIndexPath:indexPath index:index];
        }
    }];


AFNetWorking上的經(jīng)典代碼,防止循環(huán)引用的

//創(chuàng)建__weak弱引用寞忿,防止強引用互相持有
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
    //創(chuàng)建局部__strong強引用驰唬,防止多線程情況下weakSelf被析構
     __strong __typeof(weakSelf)strongSelf = weakSelf;
    strongSelf.networkReachabilityStatus = status;
    if (strongSelf.networkReachabilityStatusBlock) {
         strongSelf.networkReachabilityStatusBlock(status);
    }
};

weak 本身是可以避免循環(huán)引用的問題的,但是其會導致外部對象釋放了之后腔彰,block 內(nèi)部也訪問不到這個對象的問題叫编,我們可以通過在 block 內(nèi)部聲明一個 strong 的變量來指向 weakObj,使外部對象既能在 block 內(nèi)部保持住霹抛,又能避免循環(huán)引用的問題

block 本身無法避免循環(huán)引用的問題搓逾,但是我們可以通過在 block 內(nèi)部手動把 blockObj 賦值為 nil 的方式來避免循環(huán)引用的問題。另外一點就是 block 修飾的變量在 block 內(nèi)外都是唯一的杯拐,要注意這個特性可能帶來的隱患霞篡。


 _timer = [NSTimer timerWithTimeInterval:[refreshTime integerValue]
                                             target:self
                                           selector:@selector(doFSearchDoubleBackNumberRequest:)
                                           userInfo:searchResult
                                            repeats:NO];
            [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];

Timer 添加到 Runloop 的時候,會被 Runloop 強引用端逼。
Timer 又會有一個對 Target 的強引用朗兵。
所以說如果不對Timer進行釋放,Timer的targer(self)也一直不會被釋放顶滩。
有時候我們我們對某個Timer的targer設置了nil余掖。但沒設置[timer invalidate]。
其實這個對象還是沒被釋放的礁鲁。timer對應的執(zhí)行方法也一直會在線程中執(zhí)行浊吏。容易造成內(nèi)存泄露。

注:repeats:NO不會強引用

常規(guī)的監(jiān)測方法
  • Analyze靜態(tài)分析 (command + shift + b)

    主要分析以下四種問題:

    1救氯、邏輯錯誤:訪問空指針或未初始化的變量等找田;

    2、內(nèi)存管理錯誤:如內(nèi)存泄漏等着憨;

    3墩衙、聲明錯誤:從未使用過的變量;

    4甲抖、Api調(diào)用錯誤:未包含使用的庫和框架漆改。

    靜態(tài)分析結果會有警告提示

    image
  • Instruments中的Leak動態(tài)分析內(nèi)存泄漏

    product->profile ->leaks 打開工具主窗口

    image

    點擊暫停,將鼠標移到叉號上面點擊鎖定准谚,點擊下方的“田”字格挫剑,選擇callTree,

    選擇中間的齒輪柱衔,選中選項中的 invert Call Tree 和Hide System Libraries

  • Separate by Thread:按線程分開做分析樊破,這樣更容易揪出那些吃資源的問題線程愉棱。特別是對于主線程,它要處理和渲染所有的接口數(shù)據(jù)哲戚,一旦受到阻塞奔滑,程序必然卡頓或停止響應。

  • Invert Call Tree:反向輸出調(diào)用樹顺少。把調(diào)用層級最深的方法顯示在最上面朋其,更容易找到最耗時的操作。

  • Hide System Libraries:隱藏系統(tǒng)庫文件脆炎。過濾掉各種系統(tǒng)調(diào)用梅猿,只顯示自己的代碼調(diào)用。

  • Flattern Recursion:拼合遞歸秒裕。將同一遞歸函數(shù)產(chǎn)生的多條堆棧(因為遞歸函數(shù)會調(diào)用自己)合并為一條袱蚓。

雙擊左邊 Call Tree 下面的任意一行,查看內(nèi)存泄漏的代碼位置

image
  • Allocation工具了解內(nèi)存的分配情況

    每次點擊generations(是兩個時間標記之間所有仍然活著的對象的快照),生成快照簇爆,而且 Allocations 會記錄從上回內(nèi)存快照到這次內(nèi)存快照這個時間段內(nèi)癞松,新分配的內(nèi)存信息,數(shù)次 push 跟 pop 之后入蛆,內(nèi)存還不斷增長响蓉,則有內(nèi)存泄露

    image

    開源項目 HeapInspector-for-iOS 可以說是 Allocations 的改進,它通過 hook 掉 alloc哨毁,dealloc枫甲,retain,release 等方法扼褪,來記錄對象的生命周期想幻,親測不太好用

    其他工具用途

    • Core Data:監(jiān)測讀取、緩存未命中话浇、保存等操作脏毯,能直觀顯示是否保存次數(shù)遠超實際需要。
    • Cocoa Layout:觀察約束變化幔崖,找出布局代碼的問題所在食店。
    • Network:跟蹤 TCP / IP和 UDP / IP 連接。
    • Automations:創(chuàng)建和編輯測試腳本來自動化 iOS 應用的用戶界面測試
XCode8后新特性
  • Debug Memory Graph

    Debug Memory Graph, 直接以關系圖的形式來告訴你各個對象的持有關系, 泄露時會有紫色的小感嘆號出現(xiàn),
    在開發(fā)過程中赏寇,因為語法或明顯的代碼錯誤(例如Retain Cycle)吉嫩,編譯器可以發(fā)現(xiàn)并報黃色或紅色警告

image

實時監(jiān)測內(nèi)存占用情況

image

直接選擇一個對象,查看與其相關的內(nèi)存關系

image
- 綠色的一般都是 UIKit 控件及其子類
- 藍色一般 NSObject 類及其子類
- 黃色一般都是容器類型及其子類
- 灰色括號是指 block

第三方工具
  • MLeaksFinder

    原理還是很簡單的, 它swizzle了NavigationController的Push和Pop相關方法來管理viewController和view的生命周期, 在你Pop掉viewController的時候, 會執(zhí)行這么一段代碼


- (BOOL)willDealloc {
    __weak id weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [weakSelf assertNotDealloc];
    });
    return YES;
}
- (void)assertNotDealloc {
     NSAssert(NO, @“”);
}

3秒后執(zhí)行 [weakSelf assertNotDealloc]; 如果這個時候view和viewController已經(jīng)釋放了, 那么weakSelf應該為nil, 所以將不會觸發(fā)斷言, 否則將會打印日志, 觸發(fā)斷言.

  • 關于swizzleSEL

一種簡寫方式


void MethodSwizzle(Class c,SEL origSEL,SEL overrideSEL)  {
        Method origMethod = class_getInstanceMethod(c, origSEL);
        Method overrideMethod= class_getInstanceMethod(c, overrideSEL);
}

傳入兩個參數(shù)嗅定,原方法選擇子自娩,新方法選擇子,并通過class_getInstanceMethod()拿到對應的Method

  • 有兩種情況要考慮一下渠退。第一種情況是要復寫的方法(overridden)并沒有在目標類中實現(xiàn)忙迁,而是在其父類中實現(xiàn)了脐彩。第二種情況是這個方法已經(jīng)存在于目標類中。這兩種情況要區(qū)別對待
    (它的目的是為了使用一個重寫的方法替換掉原來的方法动漾。但重寫的方法可能是在父類中重寫的丁屎,也可能是在子類中重寫的荠锭。)

  • 對于第一種情況(重寫方法不存在)旱眯,應當先在目標類增加一個新的實現(xiàn)方法(override),然后將復寫的方法替換為原先的實現(xiàn)

  • 對于第二情況(目標類存在重寫的方法)证九。這時可以通過method_exchangeImplementations來完成交換.

標準方式

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleSEL:@selector(viewDidDisappear:) withSEL:@selector(swizzled_viewDidDisappear:)];
        [self swizzleSEL:@selector(viewWillAppear:) withSEL:@selector(swizzled_viewWillAppear:)];
        [self swizzleSEL:@selector(dismissViewControllerAnimated:completion:) withSEL:@selector(swizzled_dismissViewControllerAnimated:completion:)];
    });
}


+ (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL {
#if _INTERNAL_MLF_ENABLED

#if _INTERNAL_MLF_RC_ENABLED
    // Just find a place to set up FBRetainCycleDetector.
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            [FBAssociationManager hook];
        });
    });
#endif

    Class class = [self class];

    Method originalMethod = class_getInstanceMethod(class, originalSEL);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSEL);

    BOOL didAddMethod =
    class_addMethod(class,
                    originalSEL,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSEL,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
#endif
}

class_addMethod:是相對于實現(xiàn)來的說的删豺,將本來不存在于被操作的Class里的newMethod的實現(xiàn)添加在被操作的Class里,并使用origSel作為其選擇子

如果發(fā)現(xiàn)方法已經(jīng)存在愧怜,會失敗返回呀页,也可以用來做檢查用,我們這里是為了避免源方法沒有實現(xiàn)的情況;如果方法沒有存在,我們則先嘗試添加被替換的方法的實現(xiàn)

1.如果返回成功:則說明被替換方法沒有存在.也就是被替換的方法沒有被實現(xiàn),我們需要先把這個方法實現(xiàn),然后再執(zhí)行我們想要的效果,用我們自定義的方法去替換被替換的方法. 這里使用到的是class_replaceMethod這個方法. class_replaceMethod本身會嘗試調(diào)用class_addMethod和method_setImplementation,所以直接調(diào)用class_replaceMethod就可以了)

2.如果返回失敗:則說明被替換方法已經(jīng)存在.直接將兩個方法的實現(xiàn)交換即可

class_replaceMethod拥坛,addMethod成功完成后蓬蝶,從參數(shù)可以看出,目的是換掉method_getImplaementation(roiginMethod)的選擇子猜惋,將原方法的實現(xiàn)的SEL換成新方法的SEL

  • FBRetainCycleDetector

    能夠檢測指定對象的引用情況丸氛,并把所存在的引用循環(huán)中各對象和引用在終端進行打印

    #import <FBRetainCycleDetector/FBRetainCycleDetector.h>

    _handlerBlock = ^{
        NSLog(@"%@", self);
    };

    FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
    [detector addCandidate:self];
    NSSet *retainCycles = [detector findRetainCycles];
    NSLog(@"%@", retainCycles);

打印結果類似于:

{(
(
"-> DetailViewController ",
"-> _handlerBlock -> NSMallocBlock "
)
)}


DetailViewController通過_handlerBlock實例變量引用一個Block對象,而該Block又引用了DetailViewController對象著摔。如果不存在引用循環(huán)的話缓窜,打印的結果將是空的

原理: 循環(huán)引用可以包含任何數(shù)量的對象。一個壞的連接會導致很多環(huán)的時候谍咆,這就復雜了

image

在環(huán)中禾锤,A→B是一個壞連接,創(chuàng)建了兩個環(huán):A-B-C-D 和 A-B-C-E摹察。
這有兩個問題:

  • 不想給一個壞連接導致的兩個循環(huán)引用分別標記恩掷。
  • 不想給可能代表兩個問題的兩個循環(huán)引用一起標記,即使它們共享一個連接供嚎。

所以需要給循環(huán)引用定義簇組(clusters)黄娘,鑒于這些啟發(fā),我們寫了個算法來找到這些問題查坪。

  • 在給定的時間收集所有的環(huán)寸宏。

  • 對于每一個環(huán),提取Facebook特定的類名偿曙。

  • 對于每一個環(huán)氮凝,找到包含在環(huán)內(nèi)的被報告的最小的環(huán)。

  • 依據(jù)上面的最小環(huán)望忆,將環(huán)添加到組中罩阵。

  • 只報告最小環(huán)竿秆。

  • PLeakSniffer

通過Pod安裝后,通過以下代碼激活即可稿壁。

[[PLeakSniffer sharedInstance] installLeakSniffer];
[[PLeakSniffer sharedInstance] addIgnoreList:@[@"MySingletonController"]];

addIgnoreList可以添加一些特殊的忽略名單幽钢,比如單例這種無法正確預測泄漏的對象

原理: 如果Controller被釋放了,但其曾經(jīng)持有過的子對象如果還存在傅是,那么這些子對象就是泄漏的可疑目標

子對象(比如view)建立一個對controller的weak引用匪燕,如果Controller被釋放,這個weak引用也隨之置為nil喧笔。那怎么知道子對象沒有被釋放呢帽驯?用一個單例對象每個一小段時間發(fā)出一個ping通知去ping這個子對象,如果子對象還活著就會一個pong通知书闸。所以結論就是:如果子對象的controller已不存在尼变,但還能響應這個ping通知,那么這個對象就是可疑的泄漏對象浆劲。完整的結構可以用下圖表示

image

參考資料

iOS 中的 NSTimer

__weak與__block區(qū)別

iOS內(nèi)存泄漏自動檢測工具PLeakSniffer

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嫌术,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子牌借,更是在濱河造成了極大的恐慌度气,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件走哺,死亡現(xiàn)場離奇詭異蚯嫌,居然都是意外死亡,警方通過查閱死者的電腦和手機丙躏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門择示,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晒旅,你說我怎么就攤上這事栅盲。” “怎么了废恋?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵谈秫,是天一觀的道長。 經(jīng)常有香客問我鱼鼓,道長拟烫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任迄本,我火速辦了婚禮硕淑,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己置媳,他們只是感情好于樟,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拇囊,像睡著了一般迂曲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寥袭,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天路捧,我揣著相機與錄音,去河邊找鬼纠永。 笑死鬓长,一個胖子當著我的面吹牛谒拴,可吹牛的內(nèi)容都是我干的尝江。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼英上,長吁一口氣:“原來是場噩夢啊……” “哼炭序!你這毒婦竟也來了?” 一聲冷哼從身側響起苍日,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤惭聂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后相恃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辜纲,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年拦耐,在試婚紗的時候發(fā)現(xiàn)自己被綠了耕腾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡杀糯,死狀恐怖扫俺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情固翰,我是刑警寧澤狼纬,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站骂际,受9級特大地震影響疗琉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歉铝,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一盈简、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦送火、人聲如沸拳话。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弃衍。三九已至,卻和暖如春坚俗,著一層夾襖步出監(jiān)牢的瞬間镜盯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工猖败, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留速缆,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓恩闻,卻偏偏與公主長得像艺糜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子幢尚,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360