Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?
— Brian Kernighan
在一个项目推进的过程中,不论是在开发中或是提测后,程序总是会出现一些非我们所期待的结果,更严重者崩溃,因此我们需要去逐行排查问题代码的根源,此时我们需要用到自身的调试能力。
往往一个开发人员排查错误代码的能力能体现出其编程水平的高低。
首先我以个人认知将iOS开发的调试分为以下几类:
代码调试被视作开发人员必备技能,但是大多数人掌握的也只是简单查看变量的值,因此掌握更全面的代码调试技巧会对你在今后的开发过程中有更大的帮助。
当程序执行到断点处,你有4种操作可以继续调试
在需要单步调试的地方设置局部断点,当程序执行到相关代码时会定位到对应的代码行
添加断点
command
+ \
查看断点
设置断点是否有效
全局断点指在整个应用运行期间,一旦满足某个条件,程序即定位到触发该条件的代码行
崩溃断点(Exception)
+
按钮,选择Add Exception Breakpoint...
符号断点(Symbolic)
+
按钮,选择Add Symbolic Breakpoint...
使用场景:有一回公司的 App 出现了一个诡异的现象,在登录完成后的首页立即点击其他的 Tab 切换到其他页面,1 秒后页面自动返回到首页。为了排查这个问题,首先我去第二个页面的 Class 中找有关 popViewController 的代码并设置断点,重现后断点并没有被执行到;之后又在第二个页面的 Class 中重写了 ViewWillDisappear 方法,并添加断点,打印函数堆栈,但是堆栈内都是系统框架级 API,也没有排查出任何结果;最终选择了符号断点,设置了 [UINavigationController popViewController:]、 [UINavigationController popToViewController:animate:] 和 [UINavigationController popToRootViewControllerAnimate:] 三个断点,最终重现后断点截获到了事件,原因是登录后延迟 1 秒发送了一条 Notification,让 NavigationController 返回到 rootViewController。
通过局部添加断点,可以给断点设置过滤条件,满足条件时程序才会进入断点调试模式
添加过滤条件(condition)
Edit Breakpoint...
YES
时,进入调试状态设置忽略次数(ignore)
设置断点行为(action)
断点包括以下几种行为
po size
,bt
,expression i = 93
%B
打印断点名称,%H
打印断点调用次数,在@@
之间输入表达式用途
我们经常会为代码中有太多的NSLog而烦恼,在需要时去写一个,不需要时去删除亦或是注释掉,不仅仅让代码看上去凌乱不堪而且添加了管理成本。现在,我们可以使用断点来提供打印功能。
在需要做输出的地方添加断点,并选择Action为Log Message
,然后将你需要输出的内容添加到输入框中,并选中Automatically continue after evaluating actions
(选中此项表示程序走到该断点处不会进入调试模式,而是处理完action后继续程序执行)。
在程序正在运行中,若想查看代码执行到某个地方的变量值时,通过这种方法就可以不需要重新编译一遍项目,只需要添加一个断点即可。
当不需要输出时,只需要将该断点设置为无效即可。
// FIXME
调试命令
n/next
step over, F6s/step
step in, fn + F7finish
step out, fn + F8c/continue
goto next breakpoint, ^
+ ⌘
+ Y
expr/expression
evaluate a C/Objective-C/C++ expression(动态执行表达式)
expr [self.view setBackgroundColor:[UIColor redColor]]
p/print
print as a C/C++ basic variablepo/expr -O
print as an Objective-C objectcall
call a method
call [self.view setBackgroundColor:[UIColor redColor]]
bt
print backtrace(打印堆栈回溯)image
find code line that corresponding to specified stack(寻找栈所对应的代码行)
image lookup --address 0x000000010c95093b
x/mem read
read from the memory of the process being debugged(dump 指定地址的内存)mem write
write to the memory of the process being debugged(改写指定地址的内存)小技巧
有的时候在po
(print object)一个对象的时候,控制台会报错提示无法打印,这个时候你只要将输出对象进行强制转换即可
如po (CGRect)self.view.frame
很多情况下,sb(storyboard)、xib或是代码写好的UI控件,但在项目跑起来后却找不到或是位置、大小、颜色、约束等属性不正确,这时候一般会去再次查看sb、xib及代码的正确性,大多数情况下在认真的检查后都能找到问题所在。然而还有小部分情况下,依然很难找到问题所在,如:一个控件不显示,但是代码里面没有找到设置hidden的地方,但其实是由于层级关系被上层view所遮挡;设置好一个图片控件,添加的单击手势,可是怎么点击图片都不触发回调方法,其实是没有设置imageView的userInteractiveEnable属性为YES,等等。所以在这种对UI调整精度要求较高的情况下,我们可以采用多种UI调试的方法达到我们所想要的结果。
UI调试工具种类繁多,在此我介绍两款我使用过并极力推荐的UI调试应用:
Reveal brings powerful runtime view debugging to iOS developers. With advanced visualizations, comprehensive inspectors and the ability to modify applications on the fly, you’ll be debugging view layout and rendering problems in seconds.
Reveal给iOS开发者带来了强大的运行时视图调试功能。它拥有先进的可视化、全方面的检查器以及实时修改应用程序的能力,可以让你在分分钟内调试各种视图布局以及渲染问题。
项目环境配置
CFNetwork.framework
, QuartzCore.framework
, CoreGraphics.framework
-ObjC -lz -framework Reveal
若以上步骤操作完依然不生效,参考官方文档 - 集成Reveal:静态连接
查看界面层级
左侧导航器中,展示了所有界面中存在的View,中间主窗口为3D效果的界面展示,可以通过拖拽、缩放、平移进行交互,右侧为选中控件的属性展示。接下来我将详细介绍一下关于Reveal的使用方法。
当你成功运行一个App后,若Reveal没有自动载入,你可以点击红色方框对应按钮,选择你当前正在运行的项目,Reveal就会将该App对应的视图层级加载到主界面中。下方以包含关系列出了所有视图,双击某个视图将其单独显示在主视图中,点击红色方框左侧的左箭头返回上一级视图,点击红色箭头所指按钮可以查看约束信息。
在主界面中,你可以看到当前App运行的界面层级,顶部按钮从左到右依次是 线框、原视图、线框+原视图、缩放比例、正视图及3D视图。在主界面中,左右拖拽可以在不同角度查看,点击某个视图,会将该视图属性展示在右侧的属性检查器中。
属性检查器的能力非常强大,几乎可以修改所有关于视图相关的内容。以下是每个检查器所能查看及修改的内容:
更多的使用技巧在实际应用中会慢慢掌握,也可以访问 Reveal Knowledge Base 查询更多资料
在Xcode 6中,苹果引入了强大的视图调试工具。在调试状态下,点击 Debug –> View Debugging –> Capture View Hierarchy
或是直接点击调试工具栏的视图调试按钮进入视图调试。
启动后,如Reveal相同,可以查看App当前视图层级的3D展示
左侧依然是视图层级列表,默认包含了约束信息(若默认不是展示视图列表,需要选择View UI Hierarchy
)
选择一个控件后,对象检查器(Object Inspector)与尺寸检查器(Size Inspector)中展示了相关信息,但仅仅只能查看不支持修改。
两者对比:
// FIXME