本文从以前做的幻灯中整理而来的,主要讲一下XCode结合LLDB调试命令以及OBJC运行时的调试技巧。
一、常用宏定义
1、OPTIMIZE,Debug和Release判定
- Release编译时定义
-
当我们想要某些代码只在Debug环境下才运行可以使用此宏定义判别
2、i386与x86_64,模拟器环境判定
- 模拟器编译时定义
-
有时工程依赖的Lib库只编译了真机的代码,模拟器编译出错。为了可以模拟器调试,使用此宏略过不能编译的代码
3、__IPHONE_8_0等,编译SDK的判定
- SDK中会声明当前SDK版本定义,并且保留过往SDK版本定义
- 有时我们编写的代码可能不会在最新的SDK编译,旧版本SDK编译会有潜在问题。使用此判定针对新老SDK版本分别编写代码
-
__IPHONE_OS_VERSION_MAX_ALLOWED,此宏定义声明了当前编译的SDK版本,可进行比较
4、使用-D编译器选项为编译Target追加宏定义
- 在工程Target的Other C Flags项目中定义
- 同一份代码可能由于证书、渠道、UI的不同,会建立多个Target分别进行打包。通过编译时追加宏定义,可在运行时判定Target。
- 下例中定义了一个宏XDTarget,其值为800100。
-
每一个Target可为XDTarget定义不同的值,这样运行时就可以判别Target,也免去了手工修改的麻烦。
5、NOP,空语句
- C语言中有nop()函数,表示一条空语句。OC中没有提供。
-
可以设计一个宏进行代替,用于挂载条件断点调试使用。
二、常用调试命令
1、PO,输出对象信息
- po调用NSObject的description方法打印对象
- po 变量名
- po 内存地址
- po 可返回对象的表达式
- 如果对象没有被释放,PO会输出内容,否则只输出一个代表野指针的数字。根据此特性可在循环引用检查中判定对象是否被正常释放。
- 配合表达式进行复杂查询
2、P,输出变量的值
- p (类型)表达式
-
除了值类型,也可以直接打印结构体数据
3、Call,执行一段代码
- call (返回类型)表达式
-
调试状态下,对于点语法支持不佳。如果发现符号未找到的情况,尝试使用发送消息的方式。如果还不行,根据提示,声明类型。对于PO、P等指令同样也需要注意
可以修改值。
- gdb下可以使用set实现,但lldb下set语法含义变化了,用call替代set指令
-
配合NOP与断点,可以在运行时动态的控制程序的运行状态
-
可以执行一段代码
4、bt,打印调用栈
三、断点的使用技巧
1、一个断点可以做些什么事情?
- 可以加入一个条件,当满足此条件时触发断点。
- 或者当执行某些次后才会触发
-
可以执行若干种类的action
-
当经过此断点时,执行action但是不会中断程序
2、异常断点
-
某些crash是由程序抛出的异常导致的,比如数组越界。可以通过添加异常断点监控异常抛出的位置。
3、符号断点
- 当符号对应的方法被调用时,中断
- -/+[类 方法]
-
举个例子:当UIViewController被载入时触发,并将当前的调用栈输出
4、内存断点
- 当该块内存被调用时中断
-
可通过XCode调试界面简化指令添加
添加时,需要预先打断,找到要监视的内存地址(注意,地址每次启动都会失效)
-
可以输出值的变化。可以找出内存是何时被修改的,值是如何变化的。
四、灵活的使用调试手段
以上这些调试手段虽然看起来比较简单,但只要灵活运用,就可以为调试带来很多便利和可能性。
比如下面一个例子
图中有三个变量a,b,c。只要在NOP语句加入一个执行call命令的条件断点,通过调节断点的开闭,就可以在程序运行时动态的控制a,b,c的数值。这使得不用编写调试代码,就可以模拟各个状态,动态的调试程序分支。
此外,当程序发生异常时,一般是通过控制台报错信息,被动的定位问题所在。如果使用调试命令结合OBJC的运行时,可以主动的获知发生异常的状态。在MRC开发的时代,对于多次释放对象的问题,甚至可以一定程度的将僵尸对象还原回原始的对象,从而定位问题所在。
所以只要灵活的运用上面这些调试手段,相信您也会很快成为一个调试高手。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。