对于大型APP,每次修改重新编译,都需要几分钟时间,因此要在一次运行中尽量多的解决问题,减少编译的次数。
无论是需求的迭代,还是bug的调试,都可能会遇到陌生的代码,对于不熟悉的代码,如何调试呢?
一 打Log
只需要一行输出函数,即可看到各个方法的执行顺序,知道输入、输出值,但是对代码入侵性太强,并且频繁修改消耗过多编译的时间。
二 问写代码的人
最直接最高效。但是如果不经过思考,就直接问,下次遇到类似的问题还是不会。因此我认为最好先自己调试,如果几个小时还无法解决,再请教。
对于遇到的问题,不能仅仅当成一种需要完成的任务,而应该把它当成一种成长的机会,拥有一颗成长型的心态。解决问题的同时,自己也获得了成长,这样就不仅仅是自己在跑步,而是借助公司这辆汽车,达到远超跑步的速度。
三 控制台调试
控制台调试最核心的就是LLDB命令,LLDB是Xcode的默认调试器,它高度利用LLVM项目中的现有库,例如Clang表达式解析器和LLVM反汇编程序。
3.1 执行命令:
expression命令,简写e,最基本的命令。
运用实例:修改View的颜色
有时候我们需要找某个控件,或改某个控件的颜色,但是可能因为层级较多而不是非常明显的情况下,可以先在LLDB尝试修改,看一下预览的效果。
操作步骤如下:
1.先执行
(lldb) p self.view
查看self.view存储在变量 0强转为UIView类型,
(lldb) e (UIView *)1
3.使用 1.layer.backgroundColor=[UIColor redColor].CGColor
4.执行刷新,可以看到对应的UIView立刻生效了。因为断点会终止UI线程,所以这个命令是为了渲染修改后的界面。
(lldb) e [CATransaction flush]
实际运用 暗黑适配
我们团队今年进行了暗黑模式适配,在改别人的代码时,尤其是层级非常多的时候,用此来调整、预览,可以快速定位需要修改的控件。
3.2 打印命令
1)print:简写pri或p,其实print是"expression --"的简写('print' is an abbreviation for 'expression --')。
2)po:用来打印对象
3)call:调用方法。在调试时,想要额外调用一下某个方法,可以使用call命令,如图
call [self callMethod]
3.3 线程
1)thread jump --by N
功能:在某处断点,跳过之后的N行代码。其效果等同于注释了N行代码。
使用场景:有时候需要注释几行代码,使用此命令,不用重新编译即可立刻看到效果。
2)thread backtrace 查看线程堆栈信息,简写为bt。
3)thread return X
令某个方法直接返回X。
运用实例:
(lldb) thread return XXX
如图,在25行执行了
(lldb) thread return @"changeReturn"
可以看到- (NSString *)threadReturn 返回的值就变成了@"changeReturn"。
3.4 观察点 watchpoint
作用:可以观察某个变量,在变化时其会自动暂停断点至其相应位置。
使用方式:(lldb) watchpoint set variable name
或者右键其,点击watch variable
注意:如果需要观测self.name,则要写成self->_name。对于观测普通类型的变量,比如button的 state值,_state不存在。在LLDB调试框可以右键这个值。
使用场景:当某个变量值突然被改变,但是又找不到是在哪里改变。使用此即可跟踪其变化。
如果不使用变量观测,就需要重写某个类的某个setter方法。
四、断点调试
先来介绍一下基本的界面:
这5个按钮,分别是:
1 启动/禁用断点
2 继续执行程序
3 执行下一步
4 进入方法
5 跳出方法
这几个可视化的按钮,在LLDB中都有相应的命令,
1 breakpoint enable/disabled
2 continue
3 next
4 step
5 finish
但是因为按钮更方便,所以通常也不用命令。
4.1 异常断点
异常断点是为了抛出异常时,crash显示到具体的行,而不是main.m中。加上如图的断点即可。
4.2 符号断点
符号断点是针对某个方法执行的断点,
实际开发中,当我们操作了某个路径会执行A方法,但是A方法用于很多个地方,并不知道具体是在哪里执行的,如果是传统的方法,就全局搜索A方法,全部打上断点。实际上可以用符号断点来解决这个问题。
只要对某个方法打上符号断点,那么所有调用它的地方,都会暂停。
注意:+,-方法写清楚。:之后不能有空格
另外。对于写得比较标准的代码,几个类似的方法,最终应该调用一个统一的方法,叫做全能初始化方法,用NS_DESIGNATED_INITIALIZER标识。只需要对全能初始化方法打符号断点即可。
4.3 编辑断点
右键点击某一行,即可打开:
1)Condition
条件断点,对于循环次数非常多的for循环,有时我们需要其在指定的i值时才暂停,或者某两个变量相等时才暂停。
2)Ignore
忽视次数N,即N次之后才会暂停。
3)Action
有些问题通过断点无法解决,因为断点操作需要几秒的操作时间。而轮询类的逻辑,几秒之后其值就变化了。这类问题通常是需要打Log的,但是打Log重新编译消耗过多的时间,如何在不改变代码、重新编译的前提下打Log呢?这时Action就起了很大的作用。
(1)Debugger Command
这个功能相当强大,可以在某一行直接插入新的代码并且不用重新编译。
(2)Log Message
打Log,格式:@exp@,exp指的是变量名。%B:指的是此行所在的方法名。%H:计数器。
缺点:Log Message 适合低频的打印。如果是秒级更新的Log。不适用,每秒打印一次会非常卡。
(3)Sound:当断点到这里时,发出声音,作用和Log一样,只不过从文字变成了声音。
(4)AppleScript,可以执行苹果原生的脚本。
(5)Shell Command,可以执行shell脚本。
五 视图查看
有时候需要对可视的某个控件进行需求,这种情况可以通过视图结构,直接找到对应的类名。虽然不能直接解决问题,但是对于找到问题根源所在,有一定帮助。
5.1原生的Debug View Hierarchy
Debug View Hierarchy,无论是性能性,还是操作方便度上,都比较差。
5.2 Reveal
reveal这是一个专业的团队做的软件,非常好用,只不过需要一些配置。
详见https://www.jianshu.com/p/ced27bec87b4
5.3 Chisel
Chisel的visualize,facebook出的一个开源LLDB框架,可以在断点的时候查看某个类的视图。
六 LLDB扩展
facebook开源了chisel,可以添加脚本到LLDB。
源码:https://github.com/facebook/chisel
chisel提供了几个命令,对LLDB的功能进一步加强。
1)visualize:预览整个view的图。
相比原生的命令,看到的不仅仅是一个控件的图像,而可以是多个view的组合。
七 网络请求的抓包
对于网络请求,可以在请求的回调中打断点、Log,查看返回的结果。但是对于轮询的请求,打断点太慢,打Log在调试框中展示的内容又太多,会刷屏。因此可以用抓包工具,对所有的请求都进行了整理。
1 charles:古老的抓包工具,不想买软件的话可以免费使用。
2 proxyman,新起之秀,个人认为无论是基础功能,还是界面,都已经超越了Charles。
3 wireshark,适用于基于TCP/UDP或蓝牙协议的硬件的抓包。
八 静态分析
静态分析,可以在编码的阶段就能检测出一些潜在的问题。
原生的Analyze,以及三方的Clang,Infer,OCLint。
九 参考文献
http://lldb.llvm.org/
https://www.jianshu.com/p/d0b9fd8ffadc
https://developer.apple.com/videos/play/wwdc2018/412/?time=182