iOS调试之chisel

iOS调试之chisel

Chisel 是一个 LLDB 指令集合,用户辅助 iOS 应用差错。

安装
  1. chisel的安装需要使用Homebrew,如果还没有安装Homebrew,可以使用下面的命令安装,如果你已经安装了,可以跳过这一步

    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

  2. 用Homebrew安装chisel:

    brew update
    brew install chisel

  3. ~/.lldbinit中添加命令

    touch ~/.lldbinit
    echo "command script import /usr/local/opt/chisel/libexec/fblldb.py" >> ~/.lldbinit
    重启一下Xcode,安装完成。

更新

如果你想更新chisel,只需要输入更新的命令即可。

brew upgrade chisel

命令
Autolayout

autolayout中有一种bug叫Ambiguous Layouts,意思是你设置的约束不足以确定view的位置或大小。比如你只设置了X轴的位置,没有设置Y轴的位置。
autolayout提供了专门判断和查找这类问题的方法:

  • hasAmbiguousLayout用于判断是否存在Ambiguous Layouts.
  • _autolayoutTrace用于查找存在的Ambiguous Layouts。
alamborder

给存在Ambiguous Layouts的view加上border,方便查找哪些View存在问题。
语法:

Syntax: alamborder --color=color --width=width

  • --color/-c: border的颜色,参数为string类型,比如’red’, ‘green’, ‘magenta’等,不设置默认为红色。
  • --width/-w: border的宽度,参数为CGFloat类型,不设置默认宽度为2。
alamunborder

将alamborder设置的border去掉。
语法:

Syntax: alamunborder

paltrace

打印某个View的autolayout详细信息,相当于调用_autolayoutTrace
语法:

Syntax: paltrace <view>

  • <view>: 需要打印详细信息的view,不传参数默认为keyWindow
    (lldb) paltrace

      •UIWindow:0x7f971283fa80
      |   •UIView:0x7f9710d8f450
      |   |   *UIButton:0x7f9710d8f820'Comst'
      |   |   |   UIButtonLabel:0x7f971283ae90'Comst'
      |   |   *_UILayoutGuide:0x7f9710d913a0
      |   |   *_UILayoutGuide:0x7f9710d92100
      
      Legend:
          * - is laid out with auto layout
          + - is laid out manually, but is represented in the layout engine because translatesAutoresizingMaskIntoConstraints = YES
          • - layout engine host
    
print

在LLDB中,我们执行的最多的可能就是打印操作了,chisel专门为这类操作封装了一些打印命令。

pviews

循环打印view层级,正常情况下等效于调用recursiveDescription命令
// 下面2条命令等效
(lldb) po [self.view recursiveDescription]
(lldb) pviews self.view
语法:

pviews [--up] [--depth=depth] <aView>

  • --up/-u: 以view为起始位置,向上打印,直到打印到window层。
  • --depth/-d: 传入int类型,表示打印的层数,0表示没有限制。
    e.g: 打印一下self.view层级:
    lldb) pviews self.view
    <UIView: 0x7f9710d8f450; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f9710d8eda0>>
    | <UIButton: 0x7f9710d8f820; frame = (37 49; 45 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7f9710d8e940>>
    | | <UIButtonLabel: 0x7f971283ae90; frame = (0 6; 45 18); text = 'Comst'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7f97128170a0>>
    | <_UILayoutGuide: 0x7f9710d913a0; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x7f9710d917b0>>
    | <_UILayoutGuide: 0x7f9710d92100; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x7f9710d92290>>
    (lldb) pviews --up self.view
    <UIWindow: 0x7f971283fa80; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x7f9712840c00>; layer = <UIWindowLayer: 0x7f971283c2d0>>
    | <UIView: 0x7f9710d8f450; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f9710d8eda0>>
pvc

循环打印viewController的层级。
语法:

Syntax: pvc <aViewController>

<aViewController>: 表示要打印的viewController,不传参数默认viewController为当前的VC。
e.g: 打印一下当前VC
(lldb) pvc
<UINavigationController 0x7fe2a2813800>, state: appeared, view: <UILayoutContainerView 0x7fe2a409bb30>
| <ViewController 0x7fe2a2428450>, state: appeared, view: <UIView 0x7fe2a409c660>

pclass

循环打印class的继承关系。
语法:

Syntax: pclass <object>
<object>: 要打印继承关系的对象。
e.g: 打印一个View对象的继承关系
(lldb) pclass [UIButton new]
UIButton
| UIControl
| | UIView
| | | UIResponder
| | | | NSObject

presponder

打印响应链。
语法:

Syntax: presponder <startResponder>
<startResponder>: UIResponder对象,响应链开始位置。
e.g: 打印一个button的响应链
pclass self.btn
UIButton
| UIControl
| | UIView
| | | UIResponder
| | | | NSObject
(lldb) presponder self.btn
<UIButton: 0x7fb16342f610; frame = (65 226; 45 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7fb163410fc0>>
| <UIView: 0x7fb163425810; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb16341de90>>
| | <ViewController: 0x7fb163524a60>
| | | <UIViewControllerWrapperView: 0x7fb165900050; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb163721990>>
| | | | <UINavigationTransitionView: 0x7fb16371cf70; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x7fb163712920>>
| | | | | <UILayoutContainerView: 0x7fb163715e40; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x7fb16371f1d0>; layer = <CALayer: 0x7fb16370b2e0>>

   |    |    |    |    |    | <UINavigationController: 0x7fb164022a00>
   |    |    |    |    |    |    | <UIWindow: 0x7fb163533440; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x7fb163534680>; layer = <UIWindowLayer: 0x7fb1635241b0>>
   |    |    |    |    |    |    |    | <UIApplication: 0x7fb163700aa0>
   |    |    |    |    |    |    |    |    | <AppDelegate: 0x7fb163709d10>
ptv

打印屏幕中显示的tableView,主要是与pcells联合使用。如果有多个tableView,打印View层级中最上面的一个。
语法:

Syntax: ptv
e.g: 看看当前最上面是哪个tableView
(lldb) ptv
<UITableView: 0x7fde52811800; frame = (0 0; 414 736); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x7fde526418d0>; layer = <CALayer: 0x7fde5260adc0>; contentOffset: {0, -64}; contentSize: {414, 176}>

pcells

打印tableView中当前可见的cell,如果有多个tableView,打印View层级中最上面的tableView的可见cell。
语法:

Syntax: pcells
e.g: 看看当前可见的cell有哪些
(lldb) pcells
<__NSArrayI 0x7fde52565a00>(
<UITableViewCell: 0x7fde52551180; frame = (0 0; 414 44); text = 'BasicViewController'; autoresize = W; layer = <CALayer: 0x7fde52537140>>,
<UITableViewCell: 0x7fde5255bea0; frame = (0 44; 414 44); text = 'DateViewController'; autoresize = W; layer = <CALayer: 0x7fde5255b1a0>>,
<UITableViewCell: 0x7fde5255e2d0; frame = (0 88; 414 44); text = 'PPTViewController'; autoresize = W; layer = <CALayer: 0x7fde5255e270>>,
<UITableViewCell: 0x7fde5255fce0; frame = (0 132; 414 44); text = 'TableViewController'; autoresize = W; layer = <CALayer: 0x7fde5255fa90>>
)

pinternals

打印一个对象内部的成员变量,这个方法我一般用来看model属性。
语法:

Syntax: pinternals <object>

  • <object>: 需要打印内部成员变量的对象
    e.g: 我们来看看一个model内部属性的值
    (lldb) pinternals model
    (Model) $5 = {
    _name = 0x000000010dd1c0a0 @"老鼠爱大米"
    _URL = nil
    _array = nil
    _dictionary = nil
    _string = nil
    _model = nil
    }
pdata

对编码过的NSData进行解码打印,等效于调用-[NSString initWithData:encoding:]
语法:

Syntax: pdata [--encoding=encoding] <data>

  • <data>: 需要打印的data,NSData类型。
  • --encoding/-e: 编码类型,如果缺省默认为utf8,主要支持的类型有:
    - ascii,
    - utf8,
    - utf16, unicode,
    - utf16l (Little endian),
    - utf16b (Big endian),
    - utf32,
    - utf32l (Little endian),
    - utf32b (Big endian),
    - latin1, iso88591 (88591),
    - latin2, iso88592 (88592),
    - cp1251 (1251),
    - cp1252 (1252),
    - cp1253 (1253),
    - cp1254 (1254),
    - cp1250 (1250),
pkp

通过-valueForKeyPath:打印key path对应的值。
语法:

Syntax: pkp <keypath>

  • <keypath>: 需要打印的路径,如self.view。
pivar

打印对象成员变量。
语法:

Syntax: pivar <object> <ivarName>

  • <object>: id类型,要打印成员变量的对象。
  • <ivarName>: 成员变量的名称,注意:如果是属性,对应成员变量的名字默认有_前缀。
Find

debug的时候,我们经常需要查找一些东西,比如View,viewController等。

fvc

根据viewController的Class名字查找VC。
语法:

Syntax: fvc [--name=classNameRegex] [--view=view]

  • --name/-n: string类型参数,根据viewController的Class名字查找viewController。
  • --view/-v: UIView类型参数,根据viewController拥有的view查找viewController。
    说明:上面2个option不能同时使用,只能使用某一个。
    e.g: 我们先根据名字查找一下VC
    (lldb) fvc --name=viewcontroller
    0x7fd01a90f310 ViewController
    e.g: 如果我们知道VC的view地址,也可以根据view来查找VC
    (lldb) fvc --view=0x7fd0194194d0
    Found the owning view controller.
    <ViewController: 0x7fd01a90f310>
fv

根据view的class名字查找view。
语法:

Syntax: fv <classNameRegex>

  • <classNameRegex>: view的class名称。
    e.g: 查找一下屏幕上的UILabel
    (lldb) fv uilabel
    0x7fd01a91dc10 UILabel
taplog

将点击的view打印出来,这个命令对于查找哪个view非常有帮助。
说明:要查看的view必须能接收点击事件,也就是他的userInteractionEnabled必须为YES才能被找到,UILabel和UIImageView默认userInteractionEnabled为NO。
用法:我们需要先将程序暂停,输入taplog,程序会自己运行,这时候点击你需要查看的view,控制台上就会显示出你刚刚点击的view相关信息。
e.g: 我们先将程序暂停,输入taplog
(lldb) taplog
Process 28421 resuming
程序会自己运行,我们再点击一个UIButton:
<UIButton: 0x7fe6785284e0; frame = (54 244; 46 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7fe678528a50>>

flicker

将view闪烁一下,以便于查找view的位置。
语法:

Syntax: flicker <viewOrLayer>

  • <viewOrLayer>需要闪烁的view或者layer。
    e.g: 我们来看看self.subView的位置
    (lldb) flicker self.subView
vs

在view层级中搜索view,并显示出来。
语法:

Syntax: vs <view>

  • <view>:要查找的view
    说明:相比fv,vs主要用于显示view在屏幕上的位置,2个命令可以配合使用
    e.g: 假设我们要找屏幕上的一个view。
    首先用fv查找UIView类型的view:
    (lldb) fv uiview
    0x7fbcf37228d0 UIView
    0x7fbcf3725e90 UIView
    然后看看这2个view到底哪个是我们想要找的view
    (lldb) vs 0x7fbcf3725e90

    Use the following and (q) to quit.
    (w) move to superview
    (s) move to first subview
    (a) move to previous sibling
    (d) move to next sibling
    (p) print the hierarchy

    <UIView: 0x7fbcf3725e90; frame = (0 100; 100 100); layer = <CALayer: 0x7fbcf3712a40>>

Display

debug的时候,可能有一小半的工作是跟UI打交道,关于UI显示上的东西,也有几个命令。

caflush

刷新UI界面。一般我们用LLDB命令改变UI,UI并不会立即更新,我们需要使用caflush刷新界面。
语法:

Syntax: caflush

e.g: 我们用命令将label的背景色改为红色
(lldb) fv uilabel
0x7fb3919189d0 UILabel
(lldb) e [((UILabel*)0x7fb3919189d0) setBackgroundColor:[UIColor redColor]]
(lldb) caflush

border

给View或者layer加上border。
语法:

Syntax: border [—color=color] [--width=width] <viewOrLayer>

  • --color/-c: 边框颜色,string类型,比如:’red’, ‘green’
    ‘magenta’等,不设置默认为红色。
  • —width/-w: 边框宽度,不设置默认为2。
  • <viewOrLayer>: 需要设置边框的view或者layer。
    e.g: 给刚刚的label加上边框。
    (lldb) fv uilabel
    0x7fe713901f10 UILabel
    (lldb) border 0x7fe713901f10
unborder

去掉view或者layer的border。
语法:

Syntax: unborder <viewOrLayer>
e.g: 将刚刚加上的border去掉。

(lldb) unborder 0x7fe713901f10
mask

给view添加一个半透明的矩形mask,用来查看view的位置。
语法:

Syntax: mask [--color=color] [--alpha=alpha] <viewOrLayer>

  • --color/-c: mask的颜色,string类型,比如:’red’, ‘green’,’magenta’等,不设置默认为红色。
  • --alpha/-a: mask的透明度,不设置默认为0.5。
  • <viewOrLayer>: 需要添加mask的view或者layer。
    e.g: 假如label是隐藏的,我们给他添加一个mask,看看他的位置在哪儿
    (lldb) fv uilabel
    0x7fe713901f10 UILabel
    (lldb) mask 0x7fe713901f10
unmask

将添加的mask去掉。
语法:

Syntax: unmask <viewOrLayer>

  • <viewOrLayer>: 需要去掉mask的view或者layer。
    e.g: 我们将刚刚添加的mask去掉
    (lldb) unmask 0x7fe713901f10
    使用命令之后,我们可以看到什么都没有了,因为label是hidden的。
show

显示一个view或者layer,相当于执行view.hidden = NO。
语法:

Syntax: show <viewOrLayer>

  • <viewOrLayer>: 需要显示的view或者layer。
hide

隐藏一个view或者layer,相当于执行view.hidden = YES。
语法:

Syntax: hide <viewOrLayer>

Preview

预览功能,帮助我们用命令查看一个view或者图片的真正样子。

visualize

用预览App打开UIImage, CGImageRef, UIView, CALayer等对象。
语法:

Syntax: visualize <target>

  • <target>: 需要预览的对象,id类型
    e.g: 我们来看看某个image的样子
    (lldb) visualize image
wivar

为对象的成员变量设置watchpoint。
语法:

Syntax: wivar <object> <ivarName>

  • <object>: 需要为成员变量设置watchpoint的对象。id类型
  • <ivarName>: 成员变量的名字,注意一般属性对应的成员变量带有_前缀
    e.g: 为self.subView设置watchpoint
    (lldb) wivar self _subView
    Remember to delete the watchpoint using: watchpoint delete 1
bmessage

根据方法名设置断点
语法:

Syntax: bmessage <expression>

  • <expression>: 设置断点的方法名,如: -[MyView setFrame:], +[MyView awesomeClassMethod], -[0xabcd1234 setFrame:]等。
    说明:一般设置断点,如果这个方法本类没有实现,是父类实现的,断点是无效的。bmessage有效避免了这种缺陷,即使本类没有实现,也能设置上断点。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 不玩LLDB,不知道chisel有多强大。chisel之于LLDB,就像iPhone之于手机,前者几乎给后者重新下...
    小笨狼阅读 9,436评论 15 78
  • 1.安装Chisel源码地址: ChiselChisel 使用 homebrew 来安装,如果你没有安装homeb...
    代码干货阅读 907评论 0 1
  • LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xc...
    CoderSC阅读 1,304评论 0 2
  • iOS调试之LLDB Xcode内嵌了LLDB控制台,在Xcode代码编辑区的下方。shift + cmd + y...
    comst阅读 1,424评论 0 3
  • 年关将近,像我这样的大龄未婚男女又开始不淡定了,越来越怕回家,越来越怕跟父母沟通,而自己其实也越来越不自信了,开...
    米路阅读 348评论 0 0