工具篇- FBMemoryProfiler 内存泄漏的自动化排查框架

前言

应用开发到一定规模后,各种内存问题频频出现,还很难定位。你是否也体会过这种痛苦?随着我们工程的体量增长,代码结构变得越来越复杂。这时候很多内存问题就变得越来越难解决。一个不小心的循环引用就会导致一部分内存被一直占用。而这样的内存泄露一般都会随着代码量的增长不断的引入到项目中。手机设备的内存是一个共享资源。应用程序可能会不当的耗尽内存、崩溃,或者遭遇大幅度的性能降低。

还好现在手机的内存越来越大,但即使这样,当你的工程越来越大之后,这些不断引入的内存问题,一定会对你应用的稳定性有越来越多的影响。

现在已经存在一些开发者工具来辅助发现内存泄漏了,但是Xcode自带的工具并不好用,真的排查起来还是相对比较困难,因为很大的原因在于你并不清楚 App 到底在哪几个页面发生了泄漏!这样的人工排查与修复工程每次都得不断地重复操作。正因为如此,我们很难在迭代阶段早期就定位与修复内存问题。从代码书写初期就发现并解决掉

FBMemoryProfiler

很多同学说不知道怎么实时看自己 APP 的内存占用情况和内存泄漏的监测,下面介绍 Facebook 的一个开源库 FBMemoryProfiler

官方gif
官方gif

这套内存泄漏检测类库大概包含了以下三个文件:

FBMemoryProfiler 是几个组件的结合。其中包括 FBAllocationTracker 和 FBRetainCycleDetector。
可视化工具,直接嵌入到 App 中,可以起到在 App 中直接查看内存使用情况,并筛选潜在泄漏对象的作用

主要用于快速检测潜在的内存泄漏对象,并提供给 FBRetainCycleDetector 进行检测
这是一个用来主动追踪所有 NSObject 的子类的内存分配和释放操作的工具。

FBAllocationTracker 用于检测应用在运行时所有实例的分配。它的原理其实就是用 method swizzling 替换原本的 alloc 方法。这样就可以记录下所有的实例分配了。

在需要的时候调用 currentAllocationSummary 方法,就可以得到当前整体的实例分配情况(前提是在 main 中初始化过,下面有介绍):
NSArray<FBAllocationTrackerSummary *> *summaries =
[[FBAllocationTrackerManager sharedManager] currentAllocationSummary];

FBRetainCycleDetector 接受一个运行时的实例,然后从这个实例开始遍历它所有的属性,逐级递归。 如果发现遍历到重复的实例,就说明存在循环引用,并给出报告。
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:myObject];
NSSet *retainCycles = [detector findRetainCycles];
NSLog(@"%@", retainCycles);

安装使用

  • pod安装

    pod 'FBMemoryProfiler'
    
  • enable FBAllocationTracker:

    在mian.m文件中(注意是main.m,不是AppDelegate.m)中加入:

     #if DEBUG
            #import <FBAllocationTracker.h>
            #import <FBAssociationManager.h>
     #endif
     int main(int argc, char * argv[]) {
           
            @autoreleasepool {
              #if DEBUG
                  [FBAssociationManager hook];
                  [[FBAllocationTrackerManager sharedManager] startTrackingAllocations];
                  [[FBAllocationTrackerManager sharedManager] enableGenerations];
              #endif
                return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
          }
      }
    
  • enable FBMemoryProfiler:

    这次是去AppDelegate.m了,去加一个变量,保证MemoryProfiler不会被释放:
    {
    FBMemoryProfiler *memoryProfiler;
    }
    didFinishLaunchingWithOptions方法中添加:

     #if DEBUG
     memoryProfiler = [[FBMemoryProfiler alloc] initWithPlugins:@[[CacheCleanerPlugin new],
                                                               [RetainCycleLoggerPlugin new]]
                            retainCycleDetectorConfiguration:nil];
     [memoryProfiler enable];
     #endif
    

CacheCleanerPlugin 类的实现

  #import <Foundation/Foundation.h>
  #import <FBMemoryProfiler/FBMemoryProfiler.h>
  @interface CacheCleanerPlugin : NSObject <FBMemoryProfilerPluggable>
  @end
   
  #import "CacheCleanerPlugin.h"
  @implementation CacheCleanerPlugin
  - (void)memoryProfilerDidMarkNewGeneration {
        [[NSURLCache sharedURLCache] removeAllCachedResponses];
  }
  @end

RetainCycleLoggerPlugin 类文件的实现

    #import <Foundation/Foundation.h>
    #import <FBMemoryProfiler/FBMemoryProfiler.h>
    @interface RetainCycleLoggerPlugin : NSObject <FBMemoryProfilerPluggable>
    @end           
  #import "RetainCycleLoggerPlugin.h"
    @implementation RetainCycleLoggerPlugin
    - (void)memoryProfilerDidFindRetainCycles:(NSSet *)retainCycles
    {
        if (retainCycles.count > 0)
        {
            NSLog(@"\nretainCycles = \n%@", retainCycles);
        }
    }
    @end

通过 RetainCycleLoggerPlugin文件 如果存在循环引用,就会输出类似的内容:
通过这个线索,你就可以找到你代码中可能导致循环引用的地方了。

   {(
      (
          "-> MyObject ",
          "-> _someObject -> __NSArrayI "
      )
    )}

在所有/整个工程的代码里 DEBUG 这个宏都是有效的。建议在DEBUG模式下使用

在工程的设置属性里搜索preprocessor macros可以看到DEBUG的定义,还可以添加上自己定义的其他模式

手动检测

内存检测.gif

我们可以看到在页面跳转到TwoViewController 中时,我们可以看到FBMemoryProfiler可以捕捉到这个实例对象的存在,并且在TwoViewController页面销毁时,也发现这个实例对象也被销毁了。
我们在工程中新建文件的时候最好是工程总的文件都是项目简称开头,这样有利于我们的搜索筛选。

下面是一个使用FBMemoryProfiler 检测出循环引用造成的内存泄漏问题:



代码是这样的

@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";
}

而且开发工具不会报任何警告

自动检测

FBMemoryProfiler 除了可以这样手动调试之外,它还可以进行自动化检测。 通过它内置的两个组件 FBRetainCycleDetector 和 FBAllocationTracker,直接检测出内存中的循环引用。

在客户端上自动进行内存泄漏监测实际上就是配合使用 FBRetainCycleDetector 加定时器或者在BaseViewController中使用,达到自动的效果。

#import <FBRetainCycleDetector/FBRetainCycleDetector.h>

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

像这样 把当前的ViewController 作为检测对象,如果当前的ViewController中存在循环引用的话就会自动打印出结果,类似下面的结果。我们可以根据结果找出问题所在。

 {(
      (
          "-> MyObject ",
          "-> _someObject -> __NSArrayI "
        )
    )}

这里要说一下,findRetainCycles查询方式所使用到的算法是DFS(深度优先搜索)。所以我们最好使用属性式的全局变量。详细了解FBRetainCycleDetectorFBRetainCycleDetector工作流程

顺便说一下,自动化检测中FBRetainCycleDetector是关键,所以,想要深入研究自动化检测的同学需要详细研究下FBRetainCycleDetector,网上文章还是挺多的。

小结

不论你是巨无霸 App 还是一个小型应用,良好的内存管理都是一个好的工程习惯。通过这些工具的帮助,我们能够更为便捷地去发现和修复内存泄漏的问题,让我们省下那些去手动检测的时间,更加聚焦在写出更好的代码上。

Reference:
http://www.cocoachina.com/ios/20160419/15954.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容