iOS 源碼分析(三):MLeaksFinder

主要分析MLeaksFinder的原理和具體實現(xiàn)

Leaks

從蘋果官方文檔可知,一個app的內(nèi)存主要分3類

  • Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).

  • Abandoned memory: Memory still referenced by your application that has no useful purpose.

  • Cached memory: Memory still referenced by your application that might be used again for better performance.
    其中 Leaked memory 和 Abandoned memory 都屬于應(yīng)該釋放而沒釋放的內(nèi)存拭嫁,都是內(nèi)存泄露,而 Leaks 工具只負責檢測 Leaked memory缺脉,而不管 Abandoned memory靶瘸。在 MRC 時代 Leaked memory 很常見,因為很容易忘了調(diào)用 release俗冻,但在 ARC 時代更常見的內(nèi)存泄露是循環(huán)引用導(dǎo)致的 Abandoned memory堆巧,Leaks 工具查不出這類內(nèi)存泄露妄荔,應(yīng)用有限舰涌。

  • 對于 Abandoned memory迁杨,可以用 Instrument 的 Allocations 檢測出來。檢測方法是用 Mark Generation 的方式讥蔽,當你每次點擊 Mark Generation 時荒揣,Allocations 會生成當前 App 的內(nèi)存快照篷角,而且 Allocations 會記錄從上回內(nèi)存快照到這次內(nèi)存快照這個時間段內(nèi),新分配的內(nèi)存信息系任。缺點是需要重復(fù)操作恳蹲,其無法及時得知泄漏

  • 對于 Leaked memory,可以使用Leaks 工具檢測俩滥,適用于運行時的檢測

介紹

MLeaksFinder 提供了內(nèi)存泄露檢測更好的解決方案嘉蕾。只需要引入 MLeaksFinder,就可以自動在 App 運行過程檢測到內(nèi)存泄露的對象并立即提醒霜旧,無需打開額外的工具错忱,也無需為了檢測內(nèi)存泄露而一個個場景去重復(fù)地操作。MLeaksFinder 目前能自動檢測 UIViewController 和 UIView 對象的內(nèi)存泄露颁糟,而且也可以擴展以檢測其它類型的對象航背。
當發(fā)生內(nèi)存泄露時,MLeaksFinder 會中斷言棱貌,并準確的告訴你哪個對象泄露了。這里設(shè)計為中斷言而不是打日志讓程序繼續(xù)跑箕肃,是因為很多人不會去看日志婚脱,斷言則能強制開發(fā)者注意到并去修改,而不是犯拖延癥勺像。

優(yōu)勢

從 MLeaksFinder 的使用方法可以看出障贸,MLeaksFinder 具備以下優(yōu)點:

  • 使用簡單,不侵入業(yè)務(wù)邏輯代碼吟宦,不用打開 Instrument
  • 不需要額外的操作篮洁,你只需開發(fā)你的業(yè)務(wù)邏輯,在你運行調(diào)試時就能幫你檢測
  • 內(nèi)存泄露發(fā)現(xiàn)及時殃姓,更改完代碼后一運行即能發(fā)現(xiàn)(這點很重要袁波,你馬上就能意識到哪里寫錯了)
  • 精準瓦阐,能準確地告訴你哪個對象沒被釋放

原理

  • 其本質(zhì)也是采用 category + runtime 實現(xiàn),主要是通過AOP切面編程思想來添加額外的功能篷牌,實現(xiàn)代碼無侵入的內(nèi)存泄漏檢測
  • 其類的結(jié)構(gòu)圖示如下所示


    類的結(jié)構(gòu)圖示.jpg

具體實現(xiàn)

  • 1睡蟋、尋找釋放點
    這里主要是通過AOP思想,通過method swizzling替換了幾個釋放方法


    method swizzling替換.jpg
  • 2枷颊、追蹤泄漏
    從上面Hook的方法的具體實現(xiàn)戳杀,我們發(fā)現(xiàn)一個共通點,最后都會調(diào)用 willDealloc 方法夭苗,說明當追蹤到頁面需要釋放時信卡,會調(diào)用此方法,以 UIViewController+MemoryLeak 分類中的 dismissViewControllerAnimated:completion: 方法為例题造,查看其具體實現(xiàn)
//load方法中hook dissmiss方法
[self swizzleSEL:@selector(dismissViewControllerAnimated:completion:) withSEL:@selector(swizzled_dismissViewControllerAnimated:completion:)];

//swizzled_dismissViewControllerAnimated:completion:的具體實現(xiàn)
- (void)swizzled_dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
  [self swizzled_dismissViewControllerAnimated:flag completion:completion];
   
  UIViewController *dismissedViewController = self.presentedViewController;
  if (!dismissedViewController && self.presentingViewController) {
    dismissedViewController = self;
  }
   
  if (!dismissedViewController) return;
   //視圖控制器即將被釋放時調(diào)用 willDealloc 方法
  [dismissedViewController willDealloc];
}

查看 willDealloc 的具體實現(xiàn)

- (BOOL)willDealloc {
  //調(diào)用父類的 willDealloc 方法
  if (![super willDealloc]) {
    return NO;
  }
  //構(gòu)建堆棧信息
  [self willReleaseChildren:self.childViewControllers];
  [self willReleaseChild:self.presentedViewController];
   
  if (self.isViewLoaded) {
    [self willReleaseChild:self.view];
  }
   
  return YES;
}
  • 3傍菇、報告泄漏
    上述調(diào)用父類的 willDealloc,其實就是調(diào)用 NSObject分類中的 willDealloc 方法晌梨,其中主要做了以下幾件事
    • 首先判斷這個class是否在白名單中桥嗤,如果在則忽略 ==> 白名單機制
    • 如果當前對象在發(fā)送action則忽略它(因為 willDealloc)總會先于他們調(diào)用)
    • 設(shè)置一個weak指針,在調(diào)用 dispatch_after 在2秒后調(diào)用 assertNotDealloc 方法仔蝌,如果還沒釋放泛领,那么會進入這個方法,如果已經(jīng)釋放了敛惊,那么這個方法是進不去的渊鞋。
- (BOOL)willDealloc {
  //獲取類名
  NSString *className = NSStringFromClass([self class]);
  //判斷這個class是否在白名單中,如果是瞧挤,則忽略 -- 白名單機制
  if ([[NSObject classNamesWhitelist] containsObject:className])
    return NO;
  //UIControl的target-action機制锡宋,如果當前對象在發(fā)送action,則忽略(因為willDealloc會先于action調(diào)用)
  NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey);
  if ([senderPtr isEqualToNumber:@((uintptr_t)self)])
    return NO;
  //設(shè)置一個weak指針特恬,指向self
  __weak id weakSelf = self;
  //在2s后执俩,如果當前對象還沒有釋放,則會執(zhí)行到assertNotDealloc方法癌刽。如果已經(jīng)釋放役首,那么weakSelf則會是nil,不會走到assertNotDealloc方法
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    __strong id strongSelf = weakSelf;
    //直接中斷言显拜,即報告內(nèi)存泄漏
    [strongSelf assertNotDealloc];
  });
   
  return YES;
}
  • 4衡奥、構(gòu)建堆棧信息
    調(diào)用了父類方法的實現(xiàn)后,在顯示堆棧信息之前远荠,還需要進行堆棧信息的構(gòu)造矮固,主要是通過willReleaseChild、willReleaseChildren兩個方法譬淳,其作用是向這個對象之中的子對象調(diào)用willDealloc 釋放的方法档址,如 view 的 subviews盹兢, UINavigationController 的 viewcontrollers 等等的子對象。
//向這個對象中的子對象調(diào)用釋放的方法
- (void)willReleaseChild:(id)child {
  if (!child) {
    return;
  }
   
  [self willReleaseChildren:@[ child ]];
}
//遍歷子對象辰晕,然后將父對象的class name加上子對象的class name蛤迎,一步步構(gòu)造出一個 視圖堆棧,如果出現(xiàn)內(nèi)存泄漏含友,直接打印此對象的 視圖堆棧即可
- (void)willReleaseChildren:(NSArray *)children {
  NSArray *viewStack = [self viewStack]; //通過關(guān)聯(lián)對象獲取值 子視圖的 class name
  NSSet *parentPtrs = [self parentPtrs]; //通過關(guān)聯(lián)對象獲取 父對象的 class name
  for (id child in children) {
    NSString *className = NSStringFromClass([child class]);
    [child setViewStack:[viewStack arrayByAddingObject:className]];
    [child setParentPtrs:[parentPtrs setByAddingObject:@((uintptr_t)child)]];
    [child willDealloc];
  }
}

其整體的流程圖示如下


整體的流程圖示.jpg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末替裆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子窘问,更是在濱河造成了極大的恐慌辆童,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惠赫,死亡現(xiàn)場離奇詭異把鉴,居然都是意外死亡,警方通過查閱死者的電腦和手機儿咱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門庭砍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人混埠,你說我怎么就攤上這事怠缸。” “怎么了钳宪?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵揭北,是天一觀的道長。 經(jīng)常有香客問我吏颖,道長搔体,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任半醉,我火速辦了婚禮疚俱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缩多。我一直安慰自己计螺,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布瞧壮。 她就那樣靜靜地躺著,像睡著了一般匙握。 火紅的嫁衣襯著肌膚如雪咆槽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天圈纺,我揣著相機與錄音秦忿,去河邊找鬼麦射。 笑死,一個胖子當著我的面吹牛灯谣,可吹牛的內(nèi)容都是我干的潜秋。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼胎许,長吁一口氣:“原來是場噩夢啊……” “哼峻呛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辜窑,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤钩述,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后穆碎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牙勘,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年所禀,在試婚紗的時候發(fā)現(xiàn)自己被綠了方面。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡色徘,死狀恐怖恭金,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贺氓,我是刑警寧澤蔚叨,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站辙培,受9級特大地震影響蔑水,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扬蕊,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一搀别、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尾抑,春花似錦歇父、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至翎冲,卻和暖如春垂睬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工驹饺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钳枕,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓赏壹,卻偏偏與公主長得像鱼炒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蝌借,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容