经验之道:最有效的iOS内存泄漏检测

需求背景:

使用instruments工具来更好的调试我们的工程中新开发或者修改过的模块的内存状况。

 iOS设备性能越来越好,iOS App 也相应的变得越来越庞大,App代码的量级也在快速的增长,开发一个小的模块在工程中调试变的越来越难,通常我们是通过观察Allocation的内存变化高低,或者内存分配快照对比来寻找泄漏的情况,但这几乎是一个让人抓狂的调试方法,尤其在非常复杂的项目中,一个模块的推入与推出在Allocation上的变化微乎其微,而且受制于项目的复杂度,各种你所未知的对象的创建与销毁带来的Allocation图形高度的影响,对于观察Allocation的分配图像的高低变化来说,能够参考的意义就变的非常有限。

通过过去参与过的复杂大型的iOS项目开发经验,开发新的模块的时候,总结出了一套完整的iOS内存自测的方法,通过Instruments来逐步跟踪检测我们创建和主动销毁的对象是否真的销毁了。

在演示之前,需要强调一件事情就是,以文件夹目录作为你的空间命名很重要,请遵循开发新模块的时候使用你的模块的命名作为前缀。因为iOS没有命名空间这个东西,通常为了保证不冲突,我们都是以项目前缀+空间命名来保证文件的独立性,空间名就是目录和模块的名字了。这个在我们使用instruments进行内存检测的时候是非常重要的,接下来的使用过程就可以证实这一良好的命名习惯所带来的巨大好处,不只是优雅,更重要的是帮助。

除了命名,还有一件很重要的事情就是,你需要对你的模块在各种UI操作或者事件发生过程的情况下,对你的对象分配过程要非常非常清楚,因为这样,你才能看出相应的变化,哪些对象是应该存在的,哪些对象在某个动作结束后是必定会销毁的,这个应该很容易,你应该纯天然的就知道,因为你开发的整个逻辑。

最近在开发一个新的模块,正好需要在内存方面做一个完整的自测,既要确保效率,也要确保内存的正确分配与释放。

重要:(以下教程开始所有示例图片,请自行点击图片看大图,看的倍儿清楚!)

首先,我先上图,看一下我的命名组织结构,遵循的就是模块化的命名,因为手Q的跨部门合作,所以用部门标记作为前缀。代码首先是要让人来看的,是人在维护程序,所以可读性非常重要,在开发完这些功能后,我对于所有对象在运行过程中的创建与销毁是很清楚的。(关于类与方法命名长短对编译包执行文件大小的影响,想深挖的可以看看bang哥的文章:http://km.oa.com/group/700/articles/show/217653)


2.接下来我要进入instruments来进行内存测试,profile运行Leaks就OK了,就会进入下面看到的界面,详细讲解一下都是什么吧,这些对对象怎么分配内存的很重要。


3.然后我就需要操作模拟器来进入我所开发的功能模块,会看到非常复杂的对象分配情况,所以这一步非常关键,我只需要在搜索框搜索模块的前缀就可以只显示当前模块所涉及的对象分配与销毁情况,如下图,进入了的模块视图:


4. 接下来我执行一个环境查询的命令,再看一下执行之后的内存分配情况。


5. 我的逻辑是这样的,点击一次创建一个命令(VASDebugPlatformServerCmd),通过这个命令初始化并执行一个任务,任务结束后就销毁这个任务对象(VASDebugPlatformBaseOperation),相应的命令也作为任务的成员一起被销毁。所以,在动作执行完后我们应该可以在已销毁对象中找到这个实例,运行截图如下:


6. 我看到任务是已经被销毁了的,可是用来初始化的命令对象为什么没有被销毁,我需要深挖一下这个命令对象(VASDebugPlatformServerCmd)的引用计数到底怎么发生变化的,就需要用到下面的步骤了,按照图解去深挖它:


7.我挖到命令对象的内部,一路挖到底,我发现命令对象最终的引用计数是1,证明它还在内存中活着,截图是这样的:


8.所以,我就看看,任务对象销毁了,那任务对象到底发生了什么事 截图是这样的:


9.我就是不死心,我就是要看到,到底是不是真的呢,为了进一步佐证命令对象在内存中,我在对象内部观察了一个内存检测的通知,收到通知后弹一个alert出来,如果对象被销毁了,它肯定收不到这个通知,如下面截图所示的工作:


11.因为这是我自己写的逻辑,我很清楚对象在哪里分配内存,然后我就去查代码,命令对象到底经历了什么,从开始到结束的执行过程是什么样的:




12. 看完上面的截图,再去看命令对象的引用计数变化就知道为什么了:


13.当然,我也看出了为什么引用计数没有归零,所以,我在下面进行了修复:


14. 再次profile->Leaks , 然后我再做一次查询任务,看看这次命令对象的引用计数变化,命令对象被释放了!:


总结:

1.保持你的模块拥有一个良好的命名空间

2.请深刻并且清楚的知道,触发什么事件,执行UI动作之后,你的对象分配会是什么样的,谁此刻应该存在内存中,谁应该被销毁,然后利用上面的原理去查看,它是不是被销毁了,如果没有被销毁,那么你应该去查你的代码,到底在执行过程中,哪里没有平衡引用计数。

3.此方法在ARC和MRC的情况都是适用的,目标是观察具体哪个实例对象没有被销毁,然后根据引用计数变化跳转到代码中去确认哪里出现了内存问题。

posted @ 2018-04-09 11:12  chihbun  阅读(913)  评论(0编辑  收藏  举报