大家好,近期围绕一个检测内存泄漏的BUG折腾了三天,最后发现原因后十分郁闷,这里跟大家分享下,希望能够给大家帮助。
众所周知,内存泄漏时APP的常见问题,在iOS系统中,APP的运行内存过大会导致系统告警,如果不及时清理则会被系统Kill掉——即我们所说的OOM(关于OOM的内容可以参考https://blog.csdn.net/killer1989/article/details/107003287),所以一个良好的APP是要避免出现内存问题。
内存泄漏,广义上有很多,比如内存常驻、内存快速飙升、无主内存未释放等,这里的内存泄漏主要指无主内存未及时释放,即没有任何指针引用的一块内存,并没有释放,而是迷失在APP的运行内存中,至于如何检测到这种情况,可以参考facebook的内存组件及其相关原理,这里不做深入探讨。
言归正传,近期,在某内存泄漏工具检测时,突然发现一个怪的现象,对目标demo进行内存泄漏检测时,连接Xcode时,在xcode的控制台里,能够大量检测出内存泄漏,而kill APP后,重新启动APP(关闭Xcode),用Mac OS自带的控制台,并没有检测到泄漏,这个问题就比较“反常”了
首先,按照控制变量法,先看是不是控制台不同导致的
在打开Xcode的同时也打开Mac OS自带的控制台,Xcode的控制台和Mac OS自带的控制台同时会输出log,此时,会有比较多的泄漏,但一旦关闭Xcode,Mac OS自带的控制台就没有泄漏显示。
两个都能打出泄漏的log,排除了Xcode的控制台和Mac OS自带的控制台log等级或宏定义不同的问题,问题不在两个控制台上。
其次,看demo中制造内存泄漏的代码
这个有个细节,该demo是一个复杂环境的demo,即有多个功能包和多个场景demo组合的demo包,于是第一时间想到,用简单demo(即只有检测工具和制造泄漏的代码组成的demo,这个demo常年存在,随时可用,且与复杂demo的代码一致)进行测试,结果纯净版demo没有问题。
不是控制台的问题,也不是制造内存泄漏代码的问题,问题第一次陷入到困境。
再进行对比,难道是内存泄漏检测工具的问题?
检测工具是sdk的形式加入,按照先配置文件,再初始化定义、再log等级,sdk版本,反复多次交叉验证,结论还是简单demo都能够检测,复杂demo只能在连接xcode时检测出来,关闭xcode无法检测,这时,又发现一个新情况,简单demo检测泄漏时,制造一次泄漏,就会检测出一次泄漏 ,而复杂demo,制造一次泄漏,连接Xcode能够检测出几百上千个泄漏,而关闭Xcode一个都检测不出来。。。问题又一次陷入到困境。
难道是复杂demo,非制造泄漏的代码导致的?
为了防止额外代码的影响,从main方法和AppDelegate页面开始,直接进入制造内存泄漏的页面,页面的代码与简单demo一致,这样复杂demo执行的代码与简单demo一致,结果现象依然存在。。。。。。问题第三次陷入到困境。
寻找两者的“变量”依然是解决问题的主要方法。
检测工具一样,代码一样,也不是控制台的问题,问题变得困难起来,这时,突然想到,复杂demo除了代码复杂,里面的功能包也比较多,即sdk,会不会这些sdk在demo初始化的时候做了一些特殊的工作,比如AFnetwork里面比较经典的用load()方法进行hook。
于是查看复杂demo,一共43个sdk。。。。。。
万里长征总要人走,问题就摆在那里,开始减包
一般来说,非业务人员和熟悉代码的工程师对一个多个sdk进行减包时是比较困难的,一方面不同的sdk之前本身就有依赖关系,另一方面,sdk在配置时可能有多个设置,有时暴力的去掉时,并不能完全用“哪里报错改哪里”的方法进行解决,好在面对如此多的sdk,demo的工程师使用了pod的方式进行管理
CocoaPods是OS X和iOS下的一个第三类库管理工具,通过CocoaPods工具我们可以为项目添加被称为“Pods”的依赖库(这些类库必须是CocoaPods本身所支持的),并且可以轻松管理其版本。
具体可以参考:https://www.jianshu.com/p/b0dc4e8d872f(特别是里面对于错误的处理)
这里可能有两个坑,一个是pod install 和 pod update的区别参考https://www.jianshu.com/p/d92226205557
一个是pod本身的版本的问题,可能出现install失败的情况,这里也卡了一阵
有了pod进行管理,配置方面不成为题,在代码级的依赖按照“哪里报错改哪里”方式进行解决,就这样,开始了减包之旅,日子一天一天过,终于在第三天(是的,需要这么久),顺利减了41包,只剩一个内存泄漏检测包和QMUIKIT包(由于这个包与demo强相关,AppDelegate也继承自它,如果去掉就得完全重写demo了),这里有的同学可能有个疑问,既然只怀疑sdk的问题,志强反向思考,给简单demo里面用pod添加多容易,没有代码级依赖性,事实上我也这么做了,但最后的结果是即使添加了43个包,简单demo依然无法复现复杂demo的问题,所以复杂demo里面一定有问题。
目前来看,问题在QMUIKIT上面?
事实上,QMUIKIT是一个比较流行的UI框架,在git上面有源码和官方demo,下载测试
首先,下载sdk,放入纯净版demo,一切正常,关闭Xcode能够检测出泄漏
然后,下载官方demo,发现里面的demo和复杂demo中关于QMUIKIT的代码是一致的,故将复杂demo里面制造内存泄漏的文件拷入官方demo中,并在main方法开始调用制造内存的代码,奇怪的现象出现了,不论连接xocde与否,都不法检测出泄漏。
问题在哪里!
事实上,问题隐藏在细节里,请看到这里的同学再看一遍上面黑色加粗体的问题,思考下
问
题
隐
藏
在
细
节
里
其实这两句话体现出了一个不容易引人注意的变量
第一个是将QMUIKIT的sdk放入纯净版demo中,第二个是将复杂版demo中制造内存泄漏的文件放入QMUIKIT的demo中
两者的sdk是一个,但两者的制造内存泄漏的文件不同!看到这里同学们可能有疑问,前文说两个代码一样,还会有差异吗?
请同学们回看前文,事实上这个简单demo是一直存在的,而复杂demo是新出现的,代码一样,sdk一样,sdk配置一样,sdk初始化一样,唯独没有说制作内存泄漏代码对应的文件配置一样。
是的,由于ARC的机制,制作一个“狭义”的内存泄漏有两种常见的方式,一种是将一个类文件设置成MRC模式,创建一个NSObject对象,另一种是用malloc的方式生成一个有初始化的对象(不初始化有一定概率被系统优化掉),而两个demo使用的是第一种方式,然而,在复杂demo对应的制造内存泄露的文件中,并没有添加MRC的配置,这就意味着,真的没有内存泄漏发生,所以无法检测出问题。
。。。
问题解决了一半,这里可以解释,为什么不连接XCode检测不出来泄漏,但为什么连接Xcode时会有大量的泄漏检测出来
查看复杂demo和QMUIKIT的demo,对比两者逻辑代码、sdk版本、sdk配置、逻辑代码配置、工程配置,发现sdk的版本不一致,两个差了1的大版本,而且sdk文件结构也不同,看来问题在这里了,升级复杂demo的sdk版本,问题解决!
至此,耗时3天,BUG终于得到了解决。