UI 调试利器 Chisel 的使用
1 什么是 Chisel
Chisel 是一个 LLDB 命令集, 用于辅助 iOS 程序的调试. 它的 Github 主页在这里.
要了解 Chisel, 首先需要了解什么是 LLDB. 如果已经了解过相关知识, 请直接跳过 LLDB 相关内容.
2 LLDB 简介
众所周知, 一个典型的bug修复周期包括: 修改代码—>编译—>运行, 然后祈祷结果是对的. 其实还有更快和更简单的办法, 就是利用 LLDB, 但千万不要只把它当做值的观察器来使用!
LLDB 是一个开源调试器, 其特点是: 实现了一个"读取-求值-输出""循环, 并且拥有众多的 C++ 和 Python 插件, 并且已经被集成到了 Xcode 中.
LLDB 有众多的插件, 而 Chisel 就是它的一个利用 Python 语言写的插件集, 下面先来看看 LLDB 的基本使用.
3 LLDB 的基本使用
首先来看如下的代码, 当触发断点后, LLDB 命令行输入会出现在界面的左下角:
设置断点下面就来看看一些 LLDB 的基本命令的使用.
3.1 help 命令
最简单的命令是 help
, 它会列出 LLDB 的帮助信息, 如果忘记某个命令的用法, 则可以使用 help <command>
来打印该命令的详细信息, 比如 help print
或者 help thread
. 当然也可以这样用 help help
😝.
3.2 print 命令
有时希望打印出某个变量的值, 则可以使用 print
命令:
由于 LLDB 会自动进行命令前缀匹配, 则实际上该命令还可以这样写: prin
, pri
或 p
. 但不能使用 pr
, 因为它和 process
命令冲突.(LLDB 中规定 p
是对应 print
, 所以请放心使用)
另外结果被自动放入了一个 $0
变量中, 所以接下来还可以使用该变量. 比如 print $0 + 9
, 则会打印 108
.
在 LLDB 中, 任何以 $
符号开头的变量都可以在接下来的命令中进行使用.
3.3 expression 命令
调试的时候, 有时候想直接为某个变量赋值, 然后观察后续代码的行为. 此时就可以用 expression
命令来改变某个变量的值. 注意, 这里改变的并非 LLDB 中变量的值, 而是运行程序中变量的值!
下面就来使用 p
和 e
(分别是 print
和 expression
的缩写)做些事情.
3.4 print 命令详解
来看这条 LLDB 语句: p count = 18
. 这条语句会直接改变 count 的值并打印出来. 如果使用 help print
, 就会发现如下输出:
'print' is an abbreviation for 'expression --'.
其中的"两道杠"是用来分隔标志位和表达式.
假设实际使用时输入 e -h +17
, 此时无法分辨到底 -h
指的是标志位, 还是说变量 h
的相反数. 所以需要"两道杠"分隔符, 这样就很容易分辨了:
e -h -- +17 中 -h 指的是标志位
e -- -h +17 中 -h 指的是h的相反数
所以 print
是 expression
无标志位时候的缩写.
3.5 打印程序中的对象
我们调试的时候想打印一个对象, 但是直接执行 p object
的话, 结果往往是😂:
(NSString *) $7 = 0x0000000104da4040 @"red balloons"
甚至是😂:
(lldb) p @[ @"foo", @"bar" ]
(NSArray *) $8 = 0x00007fdb9b71b3e0 @"2 objects"
实际上我们只想打印该对象的 description
方法所输出的内容, 这时可以使用 -O
标志:
(lldb) e -O -- $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar
)
清爽了好多. 该命令的缩写是 po
(print object), 使用 po
的效果和上面一模一样:
(lldb) po $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar
)
3.6 按指定形式打印
使用 print
打印时数字, 默认是10进制:
(lldb) p 16
16
可以使用 print/<fmt>
指定打印的形式, 比如 x
表示十六进制:
(lldb) p/x 16
0x10
使用 t
表示二进制:
(lldb) p/t 16
0b00000000000000000000000000010000
(lldb) p/t (char)16
0b00010000
另外 p/c
表示字符(character), p/s
表示字符串(string, 即以 \0 结尾的 char*).
3.7 变量
LLDB 中的变量必须以 $
符号开头, 使用 LLDB 变量, 可以简化很多操作:
(lldb) e int $a = 2
(lldb) p $a * 19
38
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
2
(lldb) po [[$array objectAtIndex:0] uppercaseString]
SATURDAY
(lldb) p [[$array objectAtIndex:$a] characterAtIndex:0]
error: no known method '-characterAtIndex:'; cast the message send to the method's return type
error: 1 errors parsing expression
上述代码中 LLDB 无法区分变量类型, 所以需要给它一些提示:
(lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
'M'
(lldb) p/d (char)[[$array objectAtIndex:$a] characterAtIndex:0]
77
3.8 流程控制
可以使用 LLDB 命令来控制调试流程, 调试过程中的流程控制命令有如下四个:
- 继续:
c
- 单步:
n
- 单步进入:
s
- 单步退出:
finish
讲了这么多关于 LLDB 的内容, 下面就来看如何安装和使用 Chisel.
4 安装 Chisel
可以使用 HomeBrew 来安装 Chisel, 根据命令行提示完成安装即可:
brew update
brew install chisel
也可以直接下载 Chisel, 然后添加 ~/.lldbinit 文件, 内容如下:
# ~/.lldbinit
...
command script import /path/to/fblldb.py
注意, 写这个文档的时候, chisel是1.5.0版本, 用 homebrew 安装完成后, 有可能 .lldbinit 文件并没有被创建, 导致在 Xcode 中无法使用 chisel. 故需要在用户根目录手动创建 .lldbinit 文件, 并添加文件内容:
command script import /usr/local/opt/chisel/libexec/fblldb.py
这里该 fblldb.py 文件的位置可能有所不同, 需要根据情况修改.(该文件路径在安装 chisel 的时候会有提示)
安装完成后, 再次启动 Xcode 时, 就可以在 LLDB 中使用 Chisel 中的所有命令了.
5 如何使用 Chisel
由于 Chisel 是一个 LLDB 命令集插件, 故使用时直接调用其提供的命令即可.
要查询所有命令, 可以在 LLDB 中执行help
, 或查看 Chisel 官方命令列表: 完整命令列表.
(lldb) help
The following is a list of built-in, permanent debugger commands:
...
The following is a list of your current user-defined commands:
...
下面是一些常用命令:
命令 | 含义 | iOS | OS X |
---|---|---|---|
pviews | Print the recursive view description for the key window. | Yes | Yes |
pvc | Print the recursive view controller description for the key window. | Yes | No |
visualize | Open a UIImage , CGImageRef , UIView , CALayer , NSData (of an image), UIColor , CIColor , or CGColorRef in Preview.app on your Mac. |
Yes | No |
fv | Find a view in the hierarchy whose class name matches the provided regex. | Yes | No |
fvc | Find a view controller in the hierarchy whose class name matches the provided regex. | Yes | No |
show/hide | Show or hide the given view or layer. You don't even have to continue the process to see the changes! | Yes | Yes |
mask/unmask | Overlay a view or layer with a transparent rectangle to visualize where it is. | Yes | No |
border/unborder | Add a border to a view or layer to visualize where it is. | Yes | Yes |
caflush | Flush the render server (equivalent to a "repaint" if no animations are in-flight). | Yes | Yes |
bmessage | Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method. | Yes | Yes |
wivar | Set a watchpoint on an instance variable of an object. | Yes | Yes |
presponder | Print the responder chain starting from the given object. | Yes | Yes |
... | ... and many more! |
Chisel 特别在界面调试上有很大用途. 下面仅挑常用的说, 详细文档请移步这里.
-
show 和 hide:
比如界面上有一个label, 变量名为 textLabel, 要显示它, 则使用:
show textLabel
要隐藏, 则使用:
hide textLabel
-
alamborder 和 alamunborder:
alamborder 可以将界面上存在歧义布局(ambiguous layout)的视图标注出来, 比如标注为红色边框, 边框线宽为2, 则使用(如果指定参数, 则:
alamborder --color=red --width=2
要取消, 则使用:
alamunborder
-
border 和 unborder
将一个视图用线框包裹起来:
border xyzLabel unborder //隐藏
执行这个命令, 它会使用默认配置: 即红色边框将该视图的矩形框标识出来. 当然也可以指定颜色线宽和其他参数.
-
dismiss
将使用 present: 方法显示出来的 VC 取消显示:
dismiss xxxViewController
-
flicker
当看到一个视图变量, 但在代码中找很久都找不到它到底对应界面上什么东西的时候, 这个命令就可以出来帮忙了, 比如有一个 xyzLabel 变量, 你只知道它是个label 😂, 但如何找到它呢? 就使用如下方法, 这时 xyzLabel 会在界面上快速显隐两次, 这样就知道它在哪里了:
flicker xyzLabel
-
mwarning
模拟一个内存警告.
-
pactions
打印某个 control 的所有 action 和 target:
pactions xxxButton
-
pbcopy
打印指定对象, 且将输出拷贝到剪贴板.
-
pbundlepath
打印应用程序的 bundle 目录的地址.
-
pclass
打印某目标对象的类型继承链:
(lldb) pclass self HomeBoard_iPhone | BaseRootBoard_iPhone | | BeeUIBoard | | | UIViewController | | | | UIResponder | | | | | NSObject
-
pdata
将某 NSData 中的内容以字符串的形式打印出来, 并且可以指定编码, 默认是 utf-8:
pdata valueData
-
pdocspath
打印本应用的 Documents 目录路径.
-
pinternals
打印某对象的内部情况:
(lldb) pinternals valueData (_NSInlineData) $705 = { NSData = { NSObject = { isa = _NSInlineData } } _length = 9 }
-
pjson
以 JSON 格式打印字典或数组的内容:
(lldb) pjson @[1, 2, 3] [ 1, 2, 3 ]
-
pmethods
打印指定类或对象的所有类方法和对象方法.
-
pproperties
打印某对象或类型的所有属性
-
vs
在当前视图树中搜索指定的视图.