iOS 测试 iOS 内存检测 MLeaksFinder 二次开发

xinxi · 2019年04月19日 · 最后由 雨夜狂奔 回复于 2019年04月30日 · 4057 次阅读
本帖已被设为精华帖!

背景

在最近的 iOS 版本线上出现了两处内存泄漏并可能会导致 OOM 的风险,但是之前做专项测试并未发现.
iOS 测试很多专项指标都是使用了 xcode 的 instrument 工具,那些泄漏检查使用的是 leak 工具.

目前使用 leak 工具的几个缺点:
1.泄漏数据不准
2.工具本身难用很卡
3.依赖手工测试使用,无法自动化.

奈何 iOS 也没有像 Android LeakCanary 这种工具.一次偶然的机会看到了腾讯开源了 MLeaksFinder 工具,号称能检查到 view 层的内存泄漏,并且对代码无侵入,本文就尝试把改该工具二次开发并且引入到项目中使用.

iOS 相关知识

在正式进入正题之前,先来 review 一下 iOS 相关知识,比如生命周期、内存管理机制、内存泄漏原因.

iOS 内存管理机制

MRC

MRC(manual reference counting) 手动引用计数.

Xcode4.1 及其以前版本没有 ARC 在开发项目时我们要自己使用引用计数来管理内存,比如要手动 retain、release、autorelease 等.

eg:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 只要创建一个对象默认引用计数器的值就是1
        Person *p = [[Person alloc] init];
        NSLog(@"retainCount = %lu", [p retainCount]); // 1

        // 只要给对象发送一个retain消息, 对象的引用计数器就会+1
        [p retain];

        NSLog(@"retainCount = %lu", [p retainCount]); // 2
        // 通过指针变量p,给p指向的对象发送一条release消息
        // 只要对象接收到release消息, 引用计数器就会-1
        // 只要一个对象的引用计数器为0, 系统就会释放对象

        [p release];
        // 需要注意的是: release并不代表销毁\回收对象, 仅仅是计数器-1
        NSLog(@"retainCount = %lu", [p retainCount]); // 1

        [p release]; // 0
        NSLog(@"--------");
    }
//    [p setAge:20];    // 此时对象已经被释放
    return 0;
}

从上面代码能看出来,MRC 模式必须是遵循谁创建谁销毁对象的原则,否则就会造成对象未释放.

ARC

ARC(automatic reference counting) 自引用用计数.只要程序中开启了 ARC 模式就不需要在 retain、release、autorelease 等方法.

view 生命周期

iOS 的 view 生命周期和 Android 的 view 生命周期类似,都有从创建到销毁的过程.

image

新建 view

新建了一个view,继承UIViewController

image
`

重写了loadview、viewWillApper、viewDidApper等方法

需要注意的是在dealloc放中,不需要[super dealloc]父类的方法,因为在ARC模式已经自动化开启了自动释放

image

展示生命周期数据

打印生命周期

image

view 的跳转

push 跳转

image

pop 跳转

image

present 跳转

image

pop 返回

pop 返回的需要获取当前堆栈的中页面,所以之前的页面应该在栈顶.

image

dismiss 返回

present 和 dismiss 一般是成对使用的.

image

制作一个内存泄漏

想要知道内存泄露怎么产生,最好的方法就是自己手写一个bug来尝试分析过程及原理.

写两个view来互相跳转,来判断有内存泄漏的迹象.

image

会提示: capturing self strongly in this block is likely to lead to a retain cycle

block对于其变量都会形成强引用(retain),对于self也会形成强引用(retain),而如果self本身对block也是强引用的话,就会形成强引用循环,无法释放——造成内存泄露.

image

image

image

在产生内存泄漏的时候,不会调用dealloc方法来时释放对象.

image

常见内存泄漏

Block 循环引用

delegate 循环引用

NSTimer 循环引用

非 OC 对象内存处理

地图类处理

大次数循环内存暴涨

项目介绍

image

MLeaksFinder 不需要在代码导入任何头文件,可以自动找到 UIView 和 UIViewController 的内存泄漏.

这里使用了 AOP 技术,hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法.

集成

eg: podfile 中导入 MLeaksFinder

platform :ios, '9.0'
inhibit_all_warnings!
workspace 'SocketDemo.xcworkspace'
target "UICatalog" do

  xcodeproj 'UICatalog.xcodeproj'
  pod 'YYKit'
  pod 'Aspects'
  pod 'MLeaksFinder' 
end
post_install do |installer|
  installer.pods_project.targets.each do |target|
    puts "#{target.name}"
  end
end

效果

image

正常情况下,ViewController pop/dismiss 之后会被推出栈,进入 ViewController 的 dealloc 方法。如果没有走到 dealloc 方法中,则表明没有被释放,有内存泄露的现象.

缺点

  • c 层内存泄漏无法找到.
  • 内存泄漏提示内容较少.

改造 MLeaksFinder

MLeaksFinder 在发生那些泄漏的时候是弹出提示框,我们需要在自动化或者手动测试中无感觉的把数据上报的测试平台中.需要改造如下:

我是直接 clone 的 MLeaksFinder 代码,所以没有在本地重新 pod 一个新项目.

  • 去掉弹框

    通过阅读代码发现MLeaksMessenger.m类中会在发生内存泄漏的时候回调alertWithTitle方法.
    并且会把内存泄漏的数据"message"参数传到方法体中.
    注释或者删除[alertViewTemp show]方法就会弹出弹框提示.
    

    image

  • 上报数据

    首先需要定义后端接口地址和接口需要接收到的字段,需要view名字、泄漏详情、版本、机型、系统、uid
    (后几个参数目前固定写死,后续再考虑怎么拿到动态参数)
    

    image

在iOS发送网络请求可以使用AFNETWORK网络库或者NSURLSession网络库,NSURLSession的代码可以从postman中自动生成,直接复制代码即可.

image

提交代码

在原有的项目中把原.git 文件删除,在自己的 github 中创建项目并且把代码 push 上去

image

podspec 文件

spec 文件即一个 pod 仓库的描述信息,git 项目地址 (上面创建的 github 地址)、项目名字、tag 版本号、包含文件路径等等参数.

image

制作 TAG

制作 tag 的目的是 podspec 文件中填写的 tag 对应的.框架发布成功之后,CocoaPods 会根据 tag 信息去获取相应代码.

git tag -a 1.0.0 -m 'my version 1.0.0'

git push origin --tags

本地使用 pod repo

在项目中的 podfile 中写上 pod 名字和本地 path 路径.

eg:podfile

platform :ios, '9.0'
inhibit_all_warnings!
workspace 'SocketDemo.xcworkspace'
target "UICatalog" do

  xcodeproj 'UICatalog.xcodeproj'
  pod 'YYKit'
  pod 'Aspects'
  pod 'MLeaksFinder', :path => '/Users/xinxi/Documents/iOSProject/MLeaksFinder'  
end
post_install do |installer|
  installer.pods_project.targets.each do |target|
    puts "#{target.name}"
  end
end

在项目下指定:pod install

image

注册 pod

查看是否注册

pod trunk me

注册 pod 账号

pod trunk register xinxi@xxxx.com 'xinxi'  --verbose

image

再次执行 pod trunk me

image

发布 pod

cd MLeaksFinder

pod trunk push MLeaksFinder.podspec

image

安装 pod

搜索制作好的 pod

pod search MLeaksFinderNew

image

平台数据展示

image

结语

虽然本次二次开发的代码量很少,但是可以学习了到了 iOS 开发知识和内存泄漏的原因.

学习帖

手把手教你发布自己的 cocoapods 开源库

http://ios.jobbole.com/93284/

MLeaksFinder

https://github.com/Tencent/MLeaksFinder

ios 中的 view 生命周期

https://www.jianshu.com/p/42eb5a930d66

iOS Viewcontroller 及其他类对 dealloc 方法调用的理解

https://www.jianshu.com/p/89069ea78869

iOS 学习心得之:MRC 和 ARC 简单理解

https://www.jianshu.com/p/a16a4af3f5b9

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

https://wereadteam.github.io/2016/02/22/MLeaksFinder

iOS APP 内存泄露问题解决

https://www.jianshu.com/p/3a79eb81ff0a

iOS 之 Block 报错:capturing self strongly in this block is likely to lead to a retain cycle

https://blog.csdn.net/LVXIANGAN/article/details/50728577

共收到 1 条回复 时间 点赞
simple [精彩盘点] TesterHome 社区 2019 年 度精华帖 中提及了此贴 12月24日 22:45

虽然看不懂,但是觉得楼主好优秀啊

思寒_seveniruby 将本帖设为了精华贴 04月20日 20:17
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册