【2013年1月16日 51CTO外电头条】本文为《故障排除指南:基本技巧》。在iOS程序的数组中的三个对象莫名其妙成了五个、新开发成员的加入令游戏性不升反降:熟悉应用开发的各位朋友对这些情况一定也不陌生,这时调试就成了拨乱反正、收拾旧山河的必需手段。通过阅读本文,相信大家会对当前最重要的调试方案以及如何利用最短时间搞定问题拥有更加深刻的理解。
教程信息
- 完成时间: 30 分钟
- 执行难度: 中等
- 相关技术: iOS SDK
在本文中,我们的议题包括以下三点:
- 利用控制台检查应用程序状态;
- 进行日志记录并深入掌握NSLog操作方法;
- 通过对象生命周期追踪内存使用情况。
利用控制台进行检查
位于Xcode底端的这位仁兄称得上我们调试工作中的最佳助手。输出日志信息、错误信息以及其它各类实用信息都是它的拿手绝活,这有助于我们一步步锁定应用错误。除了直接阅读来自日志记录的输出结果,大家还可以在流程中的任意环节暂停,进而检查应用程序的各个部分。
条件断点
首先我假设大家已经了解存点的工作机制(如果还不了解也别担心,看完这几段内容您也应该基本掌握这部分知识了)。断点的作用非常重要,它能够帮我们查看应用程序在给定时间点上的所在位置——但在对象达到特定值并触发断点之后,再通过单步调试循环或者递归函数的做法实在是个痛苦的过程。这里,我们推荐大家采用条件断点。
条件断点,顾名思义是指只会在特定条件下触发的断点。设想一下,当我们只希望断点在某对象在特定状态或者“第n次”循环更替时发生,条件断点就成为大家的不二选择。在Xcode编辑器中点击“gutter”来在代码中添加一个断点 ,右击该断点并选择“edit breakpoint(编辑断点)”为其设置特殊条件。
大家可以设置条件(例如i==12)或者设定循环中断点被忽略的次数。当然,我们也可以采取其它一些自动触发机制,例如在调试命令输出值时启用断点。
提示: 利用快捷键组合coomand+\能够快速添加或删除断点。
另一项值得关注的断点技巧是添加“exception breakpoint(异常断点)”。不知道大家有没有注意到,当我们遇到异常情况时,Xcode会有99%的机率将我们引向主方法中的自动释放池。
感谢Xocde……你可真会帮倒忙。
通过设定异常断点,我们就能够精确定位引发异常情况的代码行。要做到这一点,首先得打开异常断点选项卡(快捷键为command+6)。在窗口底部会出现一个“+”按钮,点选该按钮即可添加“异常断点”。现在,一旦Xcode在运行中遭遇异常,即会在引发问题的代码处中断。
在控制台端实现手动输出
如果我们在应用的特定点进行中断,一般来说是为了检查对象的当前状态。Xcode为我们提供了一套“variables view(变量视图)”,该视图位于Xcode底部、与控制台相邻。理论上讲它的作用是显示与当前环境相关的所有值的实时状态。但在实践中,这一作用有时无法正常生效。或者是无法列出值,或者是并有将值更新为中断时的最新状态,总之问题不少。
幸运的是,我们可以利用一些非常实用的控制台命令自己进行对象检查工作。在控制台中输入“po”来获取特定对象的当前细节信息(我们使用‘p’来处理纯量值)。
这种方式在检查某对象是否已经存在(如果对象不存在则输出结果为nil)、确定对象值、查询某数组/字典在当前运行状态下的内容以及对两个对象进行比较等方面效果拔群。由于这条命令会输出相关对象的内存地址,因此我们可以对本应相同的两个对象进行输出,查看二者是否正确拥有相同的内存地址。
另一条实用但却常常被忽视的命令则是recursiveDescription,我们可以用它轻松检查视图。运行该命令后,系统会将视图结构作为结果输出。
有效日志记录
在调试程序的过程中,大家常常希望能将特定消息记录到控制台中。而“NSLog”函数允许用户将任何想要的结果输出至控制台。对于希望以特定途径梳理应用程序或者无法根据特定情况一一设定断点却仍然想要进行值比较的用户而言,它的作用相当重要。NSLog的使用格式与[NSString StringWithFormat]相同(如下图所示)。
提示: 大家可以访问https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Strings/Articles/FormatStrings.html了解更多Objictive-C中的格式化字符知识。
聪明地使用NSLog
尽管NSLog自身功能已经可圈可点,但我们还得在实际应用中再动动脑子。任何由NSLog输出的内容都会成为成品代码的一部分,也就是说会被任何接触到应用的人看到。只要把设备接入信息管理工具,每个人都能查看控制台信息并查询每一条日志记录。没错,这必然会引发一系列严重后果。试想一下,如果我们曾经向控制台输出过机密逻辑算法或者者用户密码,这些信息一旦被他人获得将带来恶劣影响。有鉴于此,公司会在App Store的审核流程中检测控制台信息。一旦发现其中包含太多输出结果,应用程序将被直接打回处进行调整。
幸运的是,我们还有更好的办法实现日志记录工作。根据大家精力与时间的富裕程度,我们拥有几种不同的处理方案。最简单的办法是做一个宏,其中只包含调试版本中的NSLog。如果时间充实,也可以采用全局可访问的头文件;我们可以把所有日志记录都灌进去,而且不用担心它们会出现在成品代码当中(前提是我们没有对预设的默认宏值做出改动——看不懂也没关系,直接用就好)。
- #ifdef DEBUG
- #define DMLog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
- #else
- #define DMLog(...) do { } while (0)
现在如果我们使用DMLog(这个名称可以随便起),它将只向调试版本输出结果,任何成品代码都不会受到影响。_PRETTY_FUNCTION_也帮上了大忙,它会根据日志信息来源为函数命名。
采取下一个步骤
虽然NSLog表现卓越,但其局限性也不可忽视:
- 它只能向本地输出结果;
- 我们无法为其添加日志“等级”(例如严重、警告等等);
- NSLog速度很慢。在进行大量处理时它会严重拖慢应用程序的运行效果。
对于想给自己来点挑战的开发者来说,还有两款框架能够回避NSLog的短板——但前辈是大家得有足够的时间和精力投入其中。下面来看这两款个人推荐:
- Cocoa LumberJack –一款专为Cocoa打造的知名通用型日志框架,学习起来略困难但功能非常强大。
- SNLog –NSLog的替换方案之一。
关注对象的生命周期
尽管自动引用计数(简称ARC)的出现令内存管理工作不再成为时间杀手,但在对象的生命周期中追踪重要事件仍然非常关键。毕竟ARC并没能消除内存泄漏或者尝试访问已释放对象的可能性(它的介入反而使二者的控制工作更加艰难)。为此,我们可以通过一系列流程及工具实现对象当前状态的监控工作。
记录重要事件
在Objective-C的对象生命周期中,最重要的两个方法分别是init与dealloc。将二者的事件记录纳入控制台非常关键,这样我们就可以从启用时开始全程监控对象,并确保其在必要时正确结束。
- - (id)init
- {
- self = [super init];
- if (self)
- {
- NSLog(@"%@: %@", NSStringFromSelector(_cmd), self);
- }
- return self;
- }
- - (void)dealloc
- {
- NSLog(@"%@: %@", NSStringFromSelector(_cmd), self);
- }
虽然输入上述代码的过程令人烦躁,但我们有办法利用自动化机制使这项工作变得更轻松。我敢保证当大家的应用程序运行出现异常时,它将起到至关重要的提示作用。大家还可以在其中使用日志片段方面的小技巧,这样结果就不会被输出到成品代码当中(甚至为我们创建一套宏)。
静分析器与检查器
Xcode中提供的两款工具能够成为我们清理代码、降低代码出错机率的好帮手。静态分析器(Static Analyzer)工具会揪出释放对象中那些未被实际使用的对象(ARC在Core Foundation对象方面无法实现这一功能),进而达到改善代码的目标。要让它伸出援手,只要在Product中选择“Anlayze”即可。
检查器(Inspector)同样是一款强大的工具,允许我们密切检测应用程序各方面的内存使用、文件系统活动等情况;它甚至还能自动模拟UI交互操作。要让它伸出援手,我们只需在Product下拉菜单中选择“Profile”选项即可。
点选之后会打开一个工具窗口,我们可以从中选择一套配置模板加以运行。最常见的选择是zombies(僵尸,这一点我们稍后再详述)、activity monitor(活动监控)以及leaks(泄露)三种。泄露方案的作用非常直观——帮助我们追踪应用程序运行中可能出现的任何内存泄露情况。
“僵尸”也是好朋友
虽然在ARC坐镇的情况下可怕的EXC_BAD_ACCESS错误的出现机率已经大大降低,但在某些极端情况下它仍然有发生的可能。在处理UIPopoverController或者核心基础对象时,我们仍然能够对过度释放的对象发起访问。通常情况下,一旦内存中的对象被释放,其只能就此消失。但当“僵尸”方案启动时,该对象只会被标记为“释放”但却仍然被保留在内存当中。如此一来,当我们访问某个僵尸对象时,Xcode会提醒我们该对象虽然能够被访问、但在实际环境中已经不应存在。在这种模式下,我们将能够了解到正常情况下无法获得的实时状态与对象位置。
我们可以通过两种方法启用僵尸对象,即在检查器中运行“僵尸”配置模板还有在“Run”选项中选择僵尸诊断方案。点击停止按钮旁边的方案名称,然后点选“Edit Scheme(编辑方案)”、单击诊断选项卡并选择“Enable Zombie Objects(启用僵尸对象)”。请注意,僵尸模式下的调试工作只能在模拟器中实现,我们无法在物理设备上完成这一诊断流程。
综述
希望本文能够帮助大家在应用程序调试工作方面拓展思路,我们的目的是尽可能缩短花费在bug修复身上的时间,并把节省下来的精力与资源投入真正重要的应用开发工作中。
当然,我们无法列出所有值得关注的技术或工具。还有很多其它方案需要留意,例如产品调试、远程bug报告、崩溃报告等等。大家在工作中有哪些心得想与我们分享?对于以上内容又有哪些疑问?请在评论栏中与我们交流。
原文链接:http://mobile.tutsplus.com/tutorials//debugging-in-ios-essential-tips/
原文标题:Debugging in iOS – Essential Tips