Mesird

iOS debugging techs

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开发的调试分为以下几类:

代码调试

代码调试被视作开发人员必备技能,但是大多数人掌握的也只是简单查看变量的值,因此掌握更全面的代码调试技巧会对你在今后的开发过程中有更大的帮助。

断点调试(Breakpoint)

当程序执行到断点处,你有4种操作可以继续调试

  1. 继续执行(continue program execution)
    该操作将继续执行程序,一直到下次碰到断点后再停止。
  2. 单步调试(step over)
    该操作将程序当前的位置向下执行一行。
  3. 进入方法体(step in)
    若当前程序位置所在行有可以进入的方法体,该操作将进入该方法体。
  4. 走出方法体(step out)
    若当前程序位置所在行在某个方法内,该操作将直接跳出该方法。

局部断点(Local)

在需要单步调试的地方设置局部断点,当程序执行到相关代码时会定位到对应的代码行

添加断点

  1. 点击代码编辑器的左侧行数,添加断点
  2. 将光标移动至需要添加断点的行,按下快捷键command + \

查看断点

设置断点是否有效

全局断点(Global)

全局断点指在整个应用运行期间,一旦满足某个条件,程序即定位到触发该条件的代码行

崩溃断点(Exception)

符号断点(Symbolic)

使用场景:有一回公司的 App 出现了一个诡异的现象,在登录完成后的首页立即点击其他的 Tab 切换到其他页面,1 秒后页面自动返回到首页。为了排查这个问题,首先我去第二个页面的 Class 中找有关 popViewController 的代码并设置断点,重现后断点并没有被执行到;之后又在第二个页面的 Class 中重写了 ViewWillDisappear 方法,并添加断点,打印函数堆栈,但是堆栈内都是系统框架级 API,也没有排查出任何结果;最终选择了符号断点,设置了 [UINavigationController popViewController:]、 [UINavigationController popToViewController:animate:] 和 [UINavigationController popToRootViewControllerAnimate:] 三个断点,最终重现后断点截获到了事件,原因是登录后延迟 1 秒发送了一条 Notification,让 NavigationController 返回到 rootViewController。

条件断点(Conditional)

通过局部添加断点,可以给断点设置过滤条件,满足条件时程序才会进入断点调试模式

添加过滤条件(condition)

设置忽略次数(ignore)

设置断点行为(action)

断点包括以下几种行为

用途

我们经常会为代码中有太多的NSLog而烦恼,在需要时去写一个,不需要时去删除亦或是注释掉,不仅仅让代码看上去凌乱不堪而且添加了管理成本。现在,我们可以使用断点来提供打印功能。

在需要做输出的地方添加断点,并选择Action为Log Message,然后将你需要输出的内容添加到输入框中,并选中Automatically continue after evaluating actions(选中此项表示程序走到该断点处不会进入调试模式,而是处理完action后继续程序执行)。

在程序正在运行中,若想查看代码执行到某个地方的变量值时,通过这种方法就可以不需要重新编译一遍项目,只需要添加一个断点即可。

当不需要输出时,只需要将该断点设置为无效即可。

LLDB

// FIXME

调试命令

小技巧

有的时候在po(print object)一个对象的时候,控制台会报错提示无法打印,这个时候你只要将输出对象进行强制转换即可

po (CGRect)self.view.frame

UI调试(User Interface)

很多情况下,sb(storyboard)、xib或是代码写好的UI控件,但在项目跑起来后却找不到或是位置、大小、颜色、约束等属性不正确,这时候一般会去再次查看sb、xib及代码的正确性,大多数情况下在认真的检查后都能找到问题所在。然而还有小部分情况下,依然很难找到问题所在,如:一个控件不显示,但是代码里面没有找到设置hidden的地方,但其实是由于层级关系被上层view所遮挡;设置好一个图片控件,添加的单击手势,可是怎么点击图片都不触发回调方法,其实是没有设置imageView的userInteractiveEnable属性为YES,等等。所以在这种对UI调整精度要求较高的情况下,我们可以采用多种UI调试的方法达到我们所想要的结果。

UI调试工具种类繁多,在此我介绍两款我使用过并极力推荐的UI调试应用:

Reveal

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开发者带来了强大的运行时视图调试功能。它拥有先进的可视化、全方面的检查器以及实时修改应用程序的能力,可以让你在分分钟内调试各种视图布局以及渲染问题。

项目环境配置

  1. 添加Reveal.framework: 启动Reveal –> Help –> Show Reveal Library in Finder,拖动添加Reveal.framework到工程中(Copy item if needed)
  2. 添加其他Library: CFNetwork.framework, QuartzCore.framework, CoreGraphics.framework
  3. 设置Target: TARGETS –> Settings –> Other Linker Flags –>添加命令 -ObjC -lz -framework Reveal
  4. 若真机调试,保证与Mac在同一WiFi环境下

若以上步骤操作完依然不生效,参考官方文档 - 集成Reveal:静态连接

查看界面层级

左侧导航器中,展示了所有界面中存在的View,中间主窗口为3D效果的界面展示,可以通过拖拽、缩放、平移进行交互,右侧为选中控件的属性展示。接下来我将详细介绍一下关于Reveal的使用方法。

当你成功运行一个App后,若Reveal没有自动载入,你可以点击红色方框对应按钮,选择你当前正在运行的项目,Reveal就会将该App对应的视图层级加载到主界面中。下方以包含关系列出了所有视图,双击某个视图将其单独显示在主视图中,点击红色方框左侧的左箭头返回上一级视图,点击红色箭头所指按钮可以查看约束信息。

在主界面中,你可以看到当前App运行的界面层级,顶部按钮从左到右依次是 线框、原视图、线框+原视图、缩放比例、正视图及3D视图。在主界面中,左右拖拽可以在不同角度查看,点击某个视图,会将该视图属性展示在右侧的属性检查器中。

属性检查器的能力非常强大,几乎可以修改所有关于视图相关的内容。以下是每个检查器所能查看及修改的内容:

更多的使用技巧在实际应用中会慢慢掌握,也可以访问 Reveal Knowledge Base 查询更多资料

Xcode - Debug View Hierarchy

在Xcode 6中,苹果引入了强大的视图调试工具。在调试状态下,点击 Debug –> View Debugging –> Capture View Hierarchy

或是直接点击调试工具栏的视图调试按钮进入视图调试。

启动后,如Reveal相同,可以查看App当前视图层级的3D展示

左侧依然是视图层级列表,默认包含了约束信息(若默认不是展示视图列表,需要选择View UI Hierarchy

选择一个控件后,对象检查器(Object Inspector)与尺寸检查器(Size Inspector)中展示了相关信息,但仅仅只能查看不支持修改。

两者对比:

  1. Xcode调试UI时,无法在真机或模拟器上进行交互,相较而言Reveal则并不影响真机或模拟器上App的运行与用户操作,若需要再次获取视图层级时,只需要在Reveal中刷新即可,这点来说Xcode略显不足。
  2. Reveal在调试UI过程中,可以在属性检查器中查看并修改控件的相关属性,并且即时生效(不得不说这点非常强大),而Xcode在调试时并不能在属性检查器中修改属性,但是可以通过LLDB命令去修改(在之前已有介绍),不过不能即时生效,而是在结束界面调试后才能生效
  3. 在 Xcode 8 之前,Xcode UI 调试无法打开带有 UITextView 的页面,该问题已在 Xcode 8 中修复。

性能调优()

// FIXME