原文连接地址:http://www.raywenderlich.com/2696/how-to-debug-memory-leaks-with-xcode-and-instruments-tutorial程序员
著做权声明:本文由http://www.cnblogs.com/andyque翻译,欢迎转载分享。请尊重做者劳动,转载时保留该声明和做者博客连接,谢谢!objective-c
教程截图:xcode
做为一名无证程序员,不管你多么精通Objective-C的内存管理,随着时间的推移,你也不可避免的犯内存相关的错误。但一般由于代码量太大,以致于你不可能一行一行的去排除(等你解决完,你设计的动车早相撞了!)安全
幸运的是,苹果已经提供了一些好的方式来帮助你找到应用程序中内存相关的问题。有时,这些工具可能吓到初学者,但它们实际上至关有用并易于掌握!app
这就是本教程说要介绍的.你会亲手使用内存工具在XCode环境下很轻松的检测内存问题。ide
这篇教程是创建在你很是熟悉Objective-C内存管理的基础上。若是你还在这个问题上找不着北,你可能须要学习内存管理其余教程。函数
第一步工具
在这一节中,咱们的目的是在一个例子应用程序中检查、解决任何内存泄漏问题,以演示常见的内存相关错误处理。开始,下载一个应用程序示例。我已经将教程和示例工程文件放在一块儿了。学习
在XCode中打开工程并运行。你会看到tableview中包含了一个寿司列表。试着选择几行,而后——轰!你看到可怕的EXC_BAD_ACCESS错误,编译器拿它彻底没有办法。ui
由于xcode彻底没指出出问题的地方,因此这种状况一般令许多开发者感到郁闷。当你遇到了一个EXC_BAD_ACCESS错误,我一般会给开发者几个建议:
1.在可执行选项中设置NSZombieEnabled参数,这有时会帮缩小问题的范围;
2.运行apple的内存检测工具,如 Leaks ,以便寻找内存问题;
3设定一个断点,单步运行代码,直到你找到引发崩溃的位置;
4.注释代码,直到不崩溃为止,而后再从后往前查找错误;
如今让咱们从第一条开始实验
# 1 - NSZombieEnabled参数
一大波僵尸正在靠近!!!!
不幸的是,NSZombieEnabled选项对于崩溃毫无办法,因此你彻底能够放弃抵抗。
当你试图使用一个已经被销毁的对象,NSZombieEnabled会标志一个警告,因此NSZombieEnabled只是一个flag。这是一个良好的开端,由于大多数崩溃的缘由都是使用了已经销毁的对象。
按照如下设置:在XCode中展开Executables->双击PropMemFun->选择Arguments选项卡->“Variables to be set in the environment”点击加号按钮。把变量名值设置成NSZombieEnabled,把值设置成YES,以下图:(xcode4在左上角,edit schema里面)
从新运行app,随便操做下使程序崩溃。查看下console log你就会看到以下信息:
2011-02-0312:07:44.778 PropMemFun[27224:207] ***
-[CFString respondsToSelector:]: message sent to deallocated instance ...
这个程序将在很精确的一行暂停。崩溃后,你能够经过选定第一个区域,回溯找出致使崩溃的准确行数。好比如今这个示例就崩溃在:tableView:didSelectRowAtIndexPath。
无论你信不信,反正找出了出问题的那行。致使崩溃的问题就是向已经销毁的string发送了一个消息。这一行用了两个string:_lastSushiSelected和sushiString.
由于这个string是由stringWithFormat初始化,因此看起来程序是没有问题了,由于stringWithFormat的返回值是自动释放的,因此在下次使用前应该是安全的。可是 _lastSushiSelected的安全性如何呢?
虽然_lastSushiSelected是在sushiString执行到最后才赋值的。可是sushiString是自动释放的,因此有些时候sushiString被释放了,内存也被销毁。可是紧接着_lastSushiSelected 仍然有可能指向被销毁的内存!这就解释了崩溃缘由:向已经销毁的内存发送消息致使崩溃。
咱们只需保留_lastSushiSelected就能够解决这个问题,把最后一行改为下面的样子:
_lastSushiSelected = [sushiString retain];
再次运行程序,你会发现程序已经畅通无阻了。
编译,分析和总结
至少,咱们有一个不崩溃的应用程序——这是一个好的开始。但接下来,咱们须要开始确保没有任何内存泄漏。
有一种简单的方法能够初步确认你的程序在初始化中是否有任何内存泄漏或其余问题--使用内置编译和分析功能(built-in Build and Analyze)。
这将使XCode执行你的代码和自动检测任何错误并警告你任何潜在的问题。它并不会找出全部的问题,但用这个方法找出的错误无疑是一个既快速又简单的方法。
试一试经过选择Build\Build and Analyze。你应该看到,它检测到一个内存泄漏,你能够看到以下:
消息显示,“alertView”有一个潜在的内存泄漏。若是你看看这一行,你就会发现全部的UIAlertView创造是有着alloc /init (返回一个对象引用数1),却历来没有真正地释放!有几种方法能够解决这个问题,但其中一个方法就是在[alertView show]下面加上一行:
[alertView release];
再次 Build\Build and Analyze,你会发现已经找不出任何内存问题了。
泄漏和管道
不幸的是,你不能依靠Build\Build and Analyze找出一切问题。有一个强大的自动化工具来帮助你检查程序是否有内存泄漏– the Leaks Instrument。
让咱们试试看。选择Run\Run -> Performance -> Tool\Leaks,再选择table view中的几行。也能够上下滚动table view,从table view顶端到底部。基于前面的经验,你就应该开始看出一些蓝色的标签出如今泄漏的内存上。
点击中止按钮,而后去工具栏中点击“Leaked Blocks”让他变成“Call Tree”。在面板左下角,点击“Invert Call Tree”、“Hide System Libraries”。你将会看到这个工具发现两个不一样的函数存在内存泄漏,你能够看到以下:
若是你双击一个函数的名字,它会带你直接到存在内存泄露的这行代码。这能够给你一个很好的错误位置提示,若是你查看代码并加以思考,你应该可以找出问题所在并解决它。
因此,为何不看看代码,而且看看你是否能找出问题所在并修正吗?一旦你做出修改,而且可以无错误提示的跑Leaks。若是经过,表示你完成了
…
…waiting…
…
…waiitng…
…
…waiting…
…!
你已经搞定了,无论你信不信,反正我是信了。
解释一下
tableView:didSelectRowAtIndexPath
Leaks 告诉咱们,这个问题的缘由是字符串sushiString创造和存储过程当中引发的内存泄漏。因此让咱们一步一步的分析一下缘由:
1.当sushiString被建立时,调用stringWithFormat。返回一个对象数值1而且发送autorelease消息。
2.在方法的最后一行,你在sushiString加入retain(retain数值增长到2)并将其存储到_lastSushiSelected。
3.后来,autorelease生效,retain数递减为1。
4.下一个tableView:didSelectRowAtIndexPath方法被调用,你重写_lastSushiSelected变量的一个指针指向一个新的字符串,- - - - -若是没有释放旧的! 因此那个老字符串并无被释放仍然存在。
一个解决办法是增长下面一行在初始化lastSushiSelected sushiString以前:
[_lastSushiSelected release];
tableView:cellForRowAtIndexPath
就像在前面的方法,建立和存入名为sushiString的变量引发内存泄漏。如下是引发问题的分析:
1.一个新的字符串被alloc/init方法建立。
2.返回一个对象引用数 1.
3.然而,这个计数历来不减小,因此有一个内存泄漏!
这能够经过三种方式中的一种解决:
1.设置textLable为一个字符串后在sushiString中调用release方法。
2.alloc/init方法初始化完毕后在sushiString中调用autorelease。
3.用stringWithFormat代替alloc/init方法,返回一个已经标志为自动释放的字符串。
验证 leaks!
修正前面提到的问题,再次运行leaks,你会获得一个没有任何内存泄漏的app。
接下来该干什么?
这个连接能够下载到一个已经解决上述问题的工程文件。
最重要的是,你必须亲自实践使用NSZombieEnabled,Build and Analyze,和Leaks Instrument工具来找到内存泄漏。你应该可以很快把这项技术运用到你的工程中。
若是你有更好的方法,能够在下面评论,我也积极采纳你们的建议。