可以通过对断点的编辑,形成不同的调试结果。
设置带判断条件的断点。
可以设置控制台输出内容的断点(也可以设置语音)
可以直接设置返回值的断点(通过lldb调试器,断点后边的值就不执行了)
添加全局断点
给全局中的函数添加断点
添加之后在 Symbol 一栏输入 viewDidLoad。
这样一来,在程序中所有的 viewDidLoad 方法被调用时都会触发断点。
当然,我们也可以仅仅为特定的某个类的方法添加断点。在 Symbol 一栏输入 [ClassName viewDidLoad] (Objective-C) 或 ClassName.viewDidLoad (Swift) 即可。
比如:unrecognized selector sent to instance 0xaxxxx 这种错误,这个instance可以这样快速定位
打印的艺术
尽管ARC已经让内存管理变得简单、省时和高效,但是在object的life-cycles中跟踪一些重要事件依然十分重要。毕竟ARC并没有完全排除内存泄露的可能性,或者试图访问一个被release的对象。为了这个目的,我们可以很艺术地偷窥对象正在做些什么,想想就好有快感。
小伙伴们第一节课学习ViewController的生命周期的时候,老师肯定很猥琐的教了大家,在viewController的每个生命周期的方法中使用了NSLog来偷窥!没错,这样其实就是最简单爆炸的跟踪生命周期的方法了,不过系统自己的NSLog真心有点羸弱,输出的信息太少,根本就不能满足我们的欲望,这里我教大家强化你的Log!!
可以用下面的这段宏
//A better version of NSLog#define NSLog(format, ...) do { \
fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
__LINE__, __func__); \
(NSLog)((format), ##__VA_ARGS__); \
fprintf(stderr, "-------\n"); \
} while (0)
在控制通过lldb调试器做调试工作。有开源库Chisel
1.print (缩写为p)输出当前断点所在模块内的值。结果中有个$0。实际上你可以使用它来指向这个结果。试试print $0 + 7,你会看到106。任何以美元符开头的东西都是存在于 LLDB 的命名空间的,它们是为了帮助你进行调试而存在的。
p objects:打印对象缩写为po
2.expression 如果想改变一个值怎么办?你或许会猜modify。其实这时候我们要用到的是expression这个方便的命令。
p count = 18。如果我们运行这条命令,然后打印count的内容。我们将看到它的结果与expression count = 18一样。
和expression不同的是,print命令不需要参数。比如e -h +17中,你很难区分到底是以-h为标识,仅仅执行+17呢,还是要计算17和h的差值。连字符号确实很让人困惑,你或许得不到自己想要的结果。
幸运的是,解决方案很简单。用--来表征标识的结束,以及输入的开始。如果想要-h作为标识,就用e -h -- +17,如果想计算它们的差值,就使用e -- -h +17。因为一般来说不使用标识的情况比较多,所以e --就有了一个简写的方式,那就是print。
3.thread return x
断点所在的函数,以某个值(x),直接做保存。
4.更新UI
有了上面的输出,我们可以获取这个 view:
(lldb) eid$myView = (id)0x7f82b1d01fd0
然后在调试器中改变它的背景色:
(lldb) e (void)[$myView setBackgroundColor:[UIColorblueColor]]
但是只有程序继续运行之后才会看到界面的变化。因为改变的内容必须被发送到渲染服务中,然后显示才会被更新。
渲染服务实际上是一个另外的进程 (被称作backboardd)。这就是说即使我们正在调试的内容所在的进程被打断了,backboardd也还是继续运行着的。
这意味着你可以运行下面的命令,而不用继续运行程序:
(lldb) e (void)[CATransactionflush]
5.Push 一个 View Controller
想象一个以UINavigationController为 root ViewController 的应用。你可以通过下面的命令,轻松地获取它:
(lldb) eid$nvc = [[[UIApplicationsharedApplication] keyWindow] rootViewController]
然后 push 一个 child view controller:
(lldb) eid$vc = [UIViewControllernew](lldb) e (void)[[$vc view] setBackgroundColor:[UIColoryellowColor]](lldb) e (void)[$vc setTitle:@"Yay!"](lldb) e (void)[$nvc pushViewContoller:$vc animated:YES]
最后运行下面的命令:
(lldb) caflush// e (void)[CATransaction flush]
查找按钮的 target
想象你在调试器中有一个$myButton的变量,可以是创建出来的,也可以是从 UI 上抓取出来的,或者是你停止在断点时的一个局部变量。你想知道,按钮按下的时候谁会接收到按钮发出的 action。非常简单:
(lldb) po [$myButtonallTargets]{( )}(lldb) po [$myButtonactionsForTarget:(id)0x7fb58bd2e240forControlEvent:0]<__NSArrayM 0x7fb58bd2aa40>(_handleTap:)
现在你或许想在它发生的时候加一个断点。在-[MagicEventListener _handleTap:]设置一个符号断点就可以了,在 Xcode 和 LLDB 中都可以,然后你就可以点击按钮并停在你所希望的地方了。
观察实例变量的变化
假设你有一个UIView,不知道为什么它的_layer实例变量被重写了 (糟糕)。因为有可能并不涉及到方法,我们不能使用符号断点。相反的,我们想监视什么时候这个地址被写入。
首先,我们需要找到_layer这个变量在对象上的相对位置:
(lldb) p (ptrdiff_t)ivar_getOffset((struct Ivar *)class_getInstanceVariable([MyViewclass], "_layer"))(ptrdiff_t) $0 =8
现在我们知道($myView + 8)是被写入的内存地址:
(lldb) watchpointsetexpression -- (int *)$myView+ 8Watchpoint created: Watchpoint 3: addr = 0x7fa554231340 size = 8 state = enabledtype= w new value: 0x0000000000000000
这被以wivar $myView _layer加入到Chisel中。
非重写方法的符号断点
假设你想知道-[MyViewController viewDidAppear:]什么时候被调用。如果这个方法并没有在MyViewController中实现,而是在其父类中实现的,该怎么办呢?试着设置一个断点,会出现以下结果:
(lldb) b -[MyViewController viewDidAppear:]
Breakpoint 1: no locations (pending).
WARNING: Unable to resolve breakpoint to any actual locations.
因为 LLDB 会查找一个符号,但是实际在这个类上却找不到,所以断点也永远不会触发。你需要做的是为断点设置一个条件[self isKindOfClass:[MyViewController class]],然后把断点放在UIViewController上。正常情况下这样设置一个条件可以正常工作。但是这里不会,因为我们没有父类的实现。
viewDidAppear:是苹果实现的方法,因此没有它的符号;在方法内没有self。如果想在符号断点上使用self,你必须知道它在哪里 (它可能在寄存器上,也可能在栈上;在 x86 上,你可以在$esp+4找到它)。但是这是很痛苦的,因为现在你必须至少知道四种体系结构 (x86,x86-64,armv7,armv64)。想象你需要花多少时间去学习命令集以及它们每一个的调用约定,然后正确的写一个在你的超类上设置断点并且条件正确的命令。幸运的是,这个在Chisel被解决了。这被成为bmessage:
(lldb) bmessage -[MyViewController viewDidAppear:]Setting a breakpoint at -[UIViewControllerviewDidAppear:] with condition (void*)object_getClass((id)$rdi) ==0x000000010e2f4d28Breakpoint1: where =UIKit`-[UIViewControllerviewDidAppear:], address =0x000000010e11533c
LLDB 和 Python
LLDB 有内建的,完整的Python支持。在LLDB中输入script,会打开一个 Python REPL。你也可以输入一行 python 语句作为script 命令的参数,这可以运行 python 语句而不进入REPL:
(lldb) scriptimportos(lldb) script os.system("open http://www.objc.io/")
这样就允许你创造各种酷的命令。把下面的语句放到文件~/myCommands.py中:
defcaflushCommand(debugger, command, result, internal_dict): debugger.HandleCommand("e (void)[CATransaction flush]")
然后再 LLDB 中运行:
command scriptimport~/myCommands.py
或者把这行命令放在/.lldbinit里,这样每次进入 LLDB 时都会自动运行。Chisel其实就是一个 Python 脚本的集合,这些脚本拼接 (命令) 字符串 ,然后让 LLDB 执行
启动视图测试:
例工程在Xcode中的三维视图展示正常,但表视图单元格似乎有点太宽了。
图26
暂停应用程序调试并在左侧选中Main.Storyboard来修复问题。点击表视图并选中Editor > Resolve Auto Layout Issues > Reset to Suggested Constraints.
图27
编译并再次运行应用程序以确定用户界面展示正常。点击Debug View Hierarchy按钮更进一步了解视图调试的功能。
视图调试功能
点击并拖拽三维渲染图的任意一边,可旋转或者倾斜用户界面,向左或者向右倾斜可选中某个表视图。
选中后,Xcode会高亮该视图,并在会在右边展示Object 和Size检查器。查看在跳转栏顶部并确认UITableView是右边最后一个项目。
图28
打开右边的Size inspector(规格检查器),下方是Auto Layout,可以看到视图上已经应用了正确的约束。在Object inspector中,我们可以检查所选视图的属性。
图29
在Xcode的调试区有9个视图调试过程中要用到的按钮和滑块儿。
图30
从左到右控件排序:
调整视图间距:调整不同视图间的间距。
展示被剪切的内容:当前展示视图中被剪切的部分。
展示约束:展示选中视图的约束。
重置查看区域:将3D渲染透视图恢复至默认状态。
调整查看模式:选择性地展示3D渲染透视图,比如仅展示内容,仅展示框架以及同时展示内容和框架。
缩小:缩小3D渲染透视图
恢复:将3D渲染透视图恢复至默认尺寸。
放大:放大3D渲染透视图
调整可视视图范围:隐藏视图或展示视图,一步步解析3D渲染视图,向左或者向右滑动滑块儿有相反的效果。
建议花一点时间上手操作下这些空间,并理解各自的用处。
视图层排序
再次编译和运行应用程序,并点击用户界面底部的"More"标签。第一眼看去界面看起来还OK,但是它没有按照开发者的定义准确执行,图片上的模糊效果没有展示出来。我们可以通过调试视图层次来更好地确定问题所在。
向左或者向右拖拽视图来查看具体情况,接着将view spacing slider向右拖动。
图31
这样一来,不同视图间的间距变大了,层次也更加清晰,我们看到在图片"下方"还隐藏着另一个视图,选中隐藏的视图,它就是"丢失"的视觉效果视图。
图32
打开Main.storyboard 并选中Second View Controller Scene。在左侧的文档概览面板中,展开Second View Controller的视图对象以查看子视图的排序。
Xcode在文档概览中按照递升顺序堆叠视图,换句话说,列表顶层的视图是视图层次的基础。
修复问题很简单。运行时,Blur Effect View隐藏在Sky Image之下,因为它是视图层次的第一个视图。在文档概览中点击并拖拽 Blur Effect View,结果会如下图展示一样:
图33
再次运行应用程序就能看到模糊效果了。应用程序的用户界面看起来符合设计的初衷。我们还可以查看iOS模拟器的其他调试功能,看看还完善了其他什么地方或功能。
iOS模拟器调试功能
编译并运行应用程序,选中模拟器,从 Debug菜单中选择Color Blended Layers选项。
图34
然后会看到app的用户界面被红色和绿色覆盖,显示了哪些图层可以被叠加覆盖,以及哪些图层是透明的。混合层属于计算密集型视图,所以推荐尽可能地使用不透明的图层。
图35
苹果在其文档(iOS Simulator User Guide)中对此进行了注明,并在表视图处理上使用了不透明图层。滚动视图时会有些表现不大好的地方,一个重要的原因就是使用了混合图层,而如果内容背景是不透明层,那么页面滚动效果就会非常流畅和平稳。
对于这款应用程序来说,假使用户有数百个项目要展示,可能会出现滚动性能不一致的情况。表视图单元格当前使用的是混合层。由于视图控制器的视图背景是白色,所以不管表视图单元格使用的是混合层或者不透明层,终端用户不会觉察到有什么不一样。
打开Main.storyboard并选中To Do list Scene中的表视图单元格属性。在属性检查器(Attributes Inspector)中,向下滚动Drawing分区并勾选Opaque。
图36
在启用Color Blended Layers的状态下编译并运行应用程序。由于表视图单元格现在使用了不透明层,所以会用绿色覆盖,以指示它们是不透明的。
除了标记图层外,还有其他一些有用的功能可帮开发者在iOS模拟器中调试应用。以下是其中一些比较有用的:
Toggle Slow Animations in Frontmost App: 选中模拟器,打开Debug菜单选中Toggle Slow Animations in Frontmost App,该功能可以降低app中动画的运行速度,适合调试包含复杂动画的应用程序。也可是使用快捷键Command-T来操作。
Color Copied Images:该选项可以给绘制时被Core Animation复制的图片添加蓝绿色叠加层。
Color Misaligned Images:如果图片边界没有与目标像素完美对齐,该功能可为图片叠加上一层品红色。如果图片使用确定的比例大小绘制,那么该功能会为图片添加一层黄色叠加。
Color Off Screen Rendered:.该选项为离屏渲染内容添加一个黄色的叠加层。
很多开发者会忽略接入电话时应用状态栏的设计问题,你可以通过触发通话中状态栏来简单测试。在iOS模拟器中,从Hardware菜单中选中Toggle In-Call Status Bar。
想查看app如何响应事件,可按下Command-T来启用slow animations,并按下Command-Y来展示电话接入时的状态栏。倘若你的应用程序使用了导航栏,那么操作系统会为你兼顾到这一块儿。
图37
除了给视图着色外,还要记住iOS模拟器也可以调试Core Location问题。你可以在特定经纬度模拟设备,