在最近的 iOS 版本线上出现了两处内存泄漏并可能会导致 OOM 的风险,但是之前做专项测试并未发现.
iOS 测试很多专项指标都是使用了 xcode 的 instrument 工具,那些泄漏检查使用的是 leak 工具.
目前使用 leak 工具的几个缺点:
1.泄漏数据不准
2.工具本身难用很卡
3.依赖手工测试使用,无法自动化.
奈何 iOS 也没有像 Android LeakCanary 这种工具.一次偶然的机会看到了腾讯开源了 MLeaksFinder 工具,号称能检查到 view 层的内存泄漏,并且对代码无侵入,本文就尝试把改该工具二次开发并且引入到项目中使用.
在正式进入正题之前,先来 review 一下 iOS 相关知识,比如生命周期、内存管理机制、内存泄漏原因.
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(automatic reference counting) 自引用用计数.只要程序中开启了 ARC 模式就不需要在 retain、release、autorelease 等方法.
iOS 的 view 生命周期和 Android 的 view 生命周期类似,都有从创建到销毁的过程.
新建了一个view,继承UIViewController
`
重写了loadview、viewWillApper、viewDidApper等方法
需要注意的是在dealloc放中,不需要[super dealloc]父类的方法,因为在ARC模式已经自动化开启了自动释放
打印生命周期
pop 返回的需要获取当前堆栈的中页面,所以之前的页面应该在栈顶.
present 和 dismiss 一般是成对使用的.
想要知道内存泄露怎么产生,最好的方法就是自己手写一个bug来尝试分析过程及原理.
写两个view来互相跳转,来判断有内存泄漏的迹象.
会提示: capturing self strongly in this block is likely to lead to a retain cycle
block对于其变量都会形成强引用(retain),对于self也会形成强引用(retain),而如果self本身对block也是强引用的话,就会形成强引用循环,无法释放——造成内存泄露.
在产生内存泄漏的时候,不会调用dealloc方法来时释放对象.
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
正常情况下,ViewController pop/dismiss 之后会被推出栈,进入 ViewController 的 dealloc 方法。如果没有走到 dealloc 方法中,则表明没有被释放,有内存泄露的现象.
MLeaksFinder 在发生那些泄漏的时候是弹出提示框,我们需要在自动化或者手动测试中无感觉的把数据上报的测试平台中.需要改造如下:
我是直接 clone 的 MLeaksFinder 代码,所以没有在本地重新 pod 一个新项目.
去掉弹框
通过阅读代码发现MLeaksMessenger.m类中会在发生内存泄漏的时候回调alertWithTitle方法.
并且会把内存泄漏的数据"message"参数传到方法体中.
注释或者删除[alertViewTemp show]方法就会弹出弹框提示.
上报数据
首先需要定义后端接口地址和接口需要接收到的字段,需要view名字、泄漏详情、版本、机型、系统、uid
(后几个参数目前固定写死,后续再考虑怎么拿到动态参数)
在iOS发送网络请求可以使用AFNETWORK网络库或者NSURLSession网络库,NSURLSession的代码可以从postman中自动生成,直接复制代码即可.
在原有的项目中把原.git 文件删除,在自己的 github 中创建项目并且把代码 push 上去
spec 文件即一个 pod 仓库的描述信息,git 项目地址 (上面创建的 github 地址)、项目名字、tag 版本号、包含文件路径等等参数.
制作 tag 的目的是 podspec 文件中填写的 tag 对应的.框架发布成功之后,CocoaPods 会根据 tag 信息去获取相应代码.
git tag -a 1.0.0 -m 'my version 1.0.0'
git push origin --tags
在项目中的 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
查看是否注册
pod trunk me
注册 pod 账号
pod trunk register xinxi@xxxx.com 'xinxi' --verbose
再次执行 pod trunk me
cd MLeaksFinder
pod trunk push MLeaksFinder.podspec
搜索制作好的 pod
pod search MLeaksFinderNew
虽然本次二次开发的代码量很少,但是可以学习了到了 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