iOS内存泄漏检测

Author Avatar
XibHe 5月 27, 2019
  • 在其它设备中阅读本文章

最近对项目中存在的内存泄露问题的检测做了一些总结,一些检测工具的使用及简单内存泄露问题的处理。

内存泄漏排查,三步走:

  1. Analyzer(静态分析)
  2. MLeaksFinder (第三方工具)
  3. Instruments Leaks (动态检测)

Analyaer

不需要运行程序,通过 Product->Analyze 或快捷键 command+shift+B 进行代码分析。

分析代码上下文的语法结构和内存情况,找出代码中潜在错误 Analyzer主要分析四种问题:

  1. 逻辑错误:访问空指针或未初始化的变量等;
  2. 内存管理错误:如内存泄漏等;
  3. 声明错误:从未使用过的变量;
  4. API 调用错误:未包含使用的库和框架。

后面提到的 CoreLocation 框架的需要手动释放问题,就是通过 Analyzer 检测出来的。

说明: Analyzer由于是编译器根据代码进行的判断, 做出的判断不一定会准确, 因此如果遇到提示, 应该去结合代码上文检查一下;还有某些造成内存泄漏的循环引用通过Analyzer分析不出来。

MLeaksFinder

通过

pod 'MLeaksFinder'

或将下载的 MLeaksFinder 文件导入工程中,

MLeaksFinder.h 中 设置是否开启内存泄漏检测,以及是在模拟器中开始还是在真机中开启。

//***内存泄漏控制开关,注释该行代码打开内存泄漏工具,打开该行代码关闭内存泄漏****
//#define MEMORY_LEAKS_FINDER_ENABLED 0

#ifdef MEMORY_LEAKS_FINDER_ENABLED

#define _INTERNAL_MLF_ENABLED MEMORY_LEAKS_FINDER_ENABLED

#else

//仅在模拟器打开内存泄漏检测
#if TARGET_IPHONE_SIMULATOR  //模拟器
    #define _INTERNAL_MLF_ENABLED DEBUG
#elif TARGET_OS_IPHONE      //真机

#endif//TARGET_IPHONE_SIMULATOR

#endif//_INTERNAL_MLF_ENABLED MEMORY_LEAKS_FINDER_ENABLED

具体操作步骤:
不断重复 pushpop 同一个 UIViewController,一旦有疑似内存泄漏的地方,就会弹出提示框,控制台也会输出调用栈的层级信息:

[文件:MLeaksMessenger.m   行数:36] Memory Leak: (
    MCLocateAddressViewController,
    UIView,
    BMKMapView,
    BMKInternalMapView,
    BMKTapDetectingView,
    BMKAnnotationContainer
)

[文件:NSObject+MemoryLeak.m   行数:54] Possibly Memory Leak.
In case that BMKAnnotationContainer should not be dealloced, override -willDealloc in BMKAnnotationContainer by returning NO.
View-ViewController stack: (
    MCLocateAddressViewController,
    UIView,
    BMKMapView,
    BMKInternalMapView,
    BMKTapDetectingView,
    BMKAnnotationContainer
)

展示视图自上而下的调用层级,这里需要注意一些三方 SDK 控件导致的疑似泄漏,可能会因其内部的 cache 机制或者释放不及时而被误判为内存泄漏。

Instruments Leaks

这里使用最新版 Xcode 10.3 进行 leaks 检测。

1. Xcode 中 按住 command + I 或者菜单栏 Product – Profile

2. 双击 Leaks 或者按 choose,打开 Leaks 面板

3. 在显示的 Leaks 面板中,点击左上角红色点,即可运行内存检测

4. 在运行过程中如果发现Leak Checks(如图)出现红色X说明检测到内存泄露,将鼠标点击Leak Checks,在下方即可看到内存泄漏的相关信息

5. 定位内存泄漏代码位置

1. 选择 Leaks 有时候默认是 Run lssues,这时需要将其切换为 Leaks
2. 选择 call Three

3. 此时,仍然看不到具体的代码位置,需要选择底部的 Call Tree,在弹窗中选择 Invert Call TreeHide System Libraries,即可显示出具体内存泄漏的代码

Call Tree 四种选项具体含义:

Separate by Thread:按线程分开做分析,这样更容易揪出那些吃资源的问题线程。特别是对于主线程,它要处理和渲染所有的接口数据,一旦受到阻塞,程序必然卡顿或停止响应。

Invert Call Tree:反向输出调用树。把调用层级最深的方法显示在最上面,更容易找到最耗时的操作。

Hide System Libraries:隐藏系统库文件。过滤掉各种系统调用,只显示自己的代码调用。

Flattern Recursion:拼合递归。将同一递归函数产生的多条堆栈(因为递归函数会调用自己)合并为一条。

4. 双击选中行,即可跳转 Instrument 中具体的代码

5. 点击右侧 Xcode 图标跳转至 Xcode 中定位的内存未回收的代码

注意

1. 注意 Core Foundation 等底层操作它们不支持 ARC,还需要手动内存管理对象的创建和释放。例如,美菜到家中绘制地图覆盖物的方法:

// 根据范围数组 border 的个数初始化定位坐标
CLLocationCoordinate2D *coords = (CLLocationCoordinate2D *)malloc(sizeof(CLLocationCoordinate2D) * borderCount);
BMKPolygon  *polygon = [BMKPolygon polygonWithCoordinates:coords count:borderCount];
free(coords);

CLLocationCoordinate2D 是 系统框架 CoreLocation 中 一个 C 的 结构体,包含经度和纬度两个值 (CLLocation 是一个 objc 的 对象),需要通过 free 手动释放。这里需要注意以 Core 开头的系统框架的内存管理。

2. 如果第五步不出现定位箭头,无法定位到具体代码,则

点击项目工程文件-Buidl Setting-All-搜索Debug Information Format-Debug里选择DWARF with dSYM File(如图)

默认情况下,只有 release 环境下为 DWARF with dSYM File,其他环境均为 DWARF。当 Debug Information FormatDWARF with dSYM File 的时候,构建过程中多了一步 Generate dSYM File,最终产出的文件也多了一个 dSYM 文件。然后,重新编译启动 Instruments Leaks,重复以上步骤即可定位到具体代码。

参考资料

MLeaksFinder:精准 iOS 内存泄露检测工具

MLeaksFinder 新特性

Tencent/MLeaksFinder

浅谈 iOS Crash

–EOF–

若无特别说明,本站文章均为原创,转载请保留链接,谢谢!