本文在「网络/蓝牙/UI/调试器/崩溃」等专题之外,对 LLDB/GDB 常用操作、Xcode 调试技巧、Chisel/Reveal/FLEX 等其它 iOS 调试方式做 SOP 与要点补充,便于日常查阅与落地使用。
📋 目录
- 一、LLDB 与 GDB
- 1.1 常用 Debug 快捷键
- 1.2 技巧一:格式化输出数据
- 1.3 技巧二:条件断点(condition)
- 1.4 技巧三:运行中修改变量的值(expr & call)
- 1.5 技巧四:符号断点(Add Symbolic Breakpoint)
- 1.6 技巧五:全局异常断点(Add Exception Breakpoint)
- 1.7 技巧六:查看整体 UI 层级结构(debug view hierarchy)
- 1.8 技巧七:开启僵尸模式(EXC_BAD_ACCESS)
- 1.9 技巧八:查看 frame 的值
- 1.10 技巧九:监听所有点击事件(UIControl、Touch、Gesture)
- 二、其它工具:Chisel
- 三、其它工具:Reveal
- 四、其它工具:FLEX
- 参考文献
一、LLDB 与 GDB
LLDB 是 Xcode 默认调试器;与 GDB 的命令对应关系可参考:lldb 与 gdb 命令对比。
1.1 常用 Debug 快捷键
| 功能 | 命令 |
|---|---|
| 暂停/继续 | Cmd + Ctrl + Y |
| 断点失效/生效 | Cmd + Y |
| 控制台显示/隐藏 | Cmd + Shift + Y |
| 光标切换到控制台 | Cmd + Shift + C |
| 清空控制台 | Cmd + K |
| Step Over | F6 |
| Step Into | F7 |
| Step Out | F8 |
1.2 技巧一:格式化输出数据
1、封装 log 函数
// Swift 版
func DLog<T>(message: T, file: String = #file, method: String = #function, line: Int = #line) {
#if DEBUG
print("\((file as NSString).lastPathComponent) : \(line), \(method) \(message)")
#endif
}
// OC 版
#ifdef DEBUG
#define DLog(fmt, ...) NSLog((@"<%s : %d> %s " fmt), [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__);
#else
#define DLog(...)
#endif
2、代替 NSLog,打印对象的内部属性
在 LLDB 中可使用 po(print object)打印对象描述;若需更细粒度,可结合 expr 或 frame variable。Xcode 控制台在断点停下时,对变量使用「右键 → Print Description of …」或输入 po 变量名 即可查看内部属性。
1.3 技巧二:条件断点(condition)
设置断点后,可为断点添加触发条件,只有条件为真时才暂停,便于在循环或高频调用处精确定位。
注意:在条件表达式中调用 Objective-C 方法时,需强转返回值类型,否则可能报错:
// 正确
(BOOL)[pId isEqualToString:@"short-videopage"]
// 报错:error: no known method '-isEqualToString:'; cast the message send to the method's return type
[pId isEqualToString:@"short-videopage"]
1.4 技巧三:运行中修改变量的值(expr & call)
在断点处可通过 Expression (expr) 或 Debug → Debug Workflow → Evaluate Expression 修改变量或调用方法,无需重新运行。例如:调试登录时临时改 token/登录状态,或调试 UI 时改某控件的颜色、frame。
在调试登录相关的 bug 时,非常方便,不用担心经常输密码,还输错的尴尬
调试 UI,改变指定控件的颜色
1.5 技巧四:符号断点(Add Symbolic Breakpoint)
通过 Add Symbolic Breakpoint 可对符号名(函数/方法)下断点,无需指定具体文件行号。适合在陌生项目中快速了解执行路径,例如对所有 viewDidLoad 下断点,观察页面加载顺序。
Symbol 填写格式:
| 语言/风格 | 写法说明 |
|---|---|
| C 语言 | methodName 只需写函数名,不用写后面的 () |
| Objective-C | [ClassName methodName],ClassName 为类名,methodName 为方法名(不区分类方法/实例方法) |
| Swift | ClassName.methodName |
- Module:模块筛选,避免不同库中同名方法/函数冲突。
- Condition:触发条件。可写表达式(如第一个参数不能为 nil);参数可用
$arg3、$arg4等表示(如$arg3 == nil)。也可调用返回 BOOL 的类方法。
样例:找出给[UIImage imageNamed:]传 nil 的调用。Symbol 设为[UIImage imageNamed:],Condition 设为$arg3 == nil,运行中一旦传 nil 就会触发断点。
如何查某个函数的符号:在该函数处打普通断点,运行到断点后,在堆栈信息中查看对应帧的显示格式,即可得到 Symbol 应填的格式。
1.6 技巧五:全局异常断点(Add Exception Breakpoint)
添加 Exception Breakpoint 后,当发生 Objective-C / C++ 异常(或可选 Swift 异常)时,调试器会在抛出处暂停,便于快速定位未捕获异常。
1.7 技巧六:查看整体 UI 层级结构(debug view hierarchy)
Xcode 菜单 Debug → View Debugging → Capture View Hierarchy 可捕获当前界面的视图层级并做 3D 展示与选择。若机器配置较低、卡顿明显,可改用 Chisel 的 pviews 等命令在控制台输出层级文本(参见本文第二节)。
1.8 技巧七:开启僵尸模式(EXC_BAD_ACCESS)
EXC_BAD_ACCESS 常表示向已释放对象发消息。开启 Zombie Objects 后,这类访问会被系统标记,Xcode 可据此在诊断中给出对象类型与释放相关信息,便于定位野指针。
开启步骤:Edit Scheme → Run → Diagnostics,勾选 Enable Zombie Objects。
1.9 技巧八:查看 frame 的值
在 LLDB 中打印 UIView 的 frame 等属性时,若直接 p self.view.frame 可能报「property 'frame' not found」。可先 导入 UIKit 模块,再打印:
(lldb) p self.view.frame
error: property 'frame' not found on object of type 'UIView *'
error: 1 errors parsing expression
(lldb) e @import UIKit
(lldb) p self.view.frame
(CGRect) $0 = (origin = (x = 0, y = 0), size = (width = 375, height = 667))
或使用强制转换:
print (CGRect)[view frame]
(CGRect) $1 = (origin = (x = 0, y = 0), size = (width = 200, height = 100))
1.10 技巧九:监听所有点击事件(UIControl、Touch、Gesture)
方法:覆写 UIApplication
通过自定义 UIApplication 子类并重写 sendEvent:,可在事件派发前统一拦截,用于统计、调试或行为分析。
.h 文件:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface CustomApplication : UIApplication
@end
NS_ASSUME_NONNULL_END
.m 文件:
#import "CustomApplication.h"
@implementation CustomApplication
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
}
@end
main.m 文件:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "CustomApplication.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc,
argv,
NSStringFromClass([CustomApplication class]),
NSStringFromClass([AppDelegate class]));
}
}
方法执行与事件次数
一次事件可能会执行三次函数:
-(void)sendEvent:(UIEvent *)event,三次的force有区别
一次事件可能会执行两次函数:
-(void)sendEvent:(UIEvent *)event,两次的force没区别
响应者链条
1、若是 UIControl 事件,继承自 UIResponder 的控件(如 UIButton)消息传递链(倒序)如下图所示
2、若是 UIGestureRecognizer 手势事件,继承自 UIResponder 的控件(如 UIView)消息传递链(倒序)如下图所示
3、若 UIControl 和 UIGestureRecognizer 同时存在,优先级关系如下图所示
二、其它工具:Chisel
Chisel 是 Facebook(Meta)开源的 LLDB 命令集合,在 Xcode 调试时提供如 pviews、pvc、fv、visualize、bmessage 等高层命令,便于查看视图层级、查找视图、可视化图片、对方法下符号断点等。
安装
使用 Homebrew 安装:
brew update
brew install chisel
安装完成后,将下面一行加入 ~/.lldbinit,Xcode 启动时才会加载 Chisel:
# Intel Mac 常见路径
command script import /usr/local/opt/chisel/libexec/fbchisellldb.py
# Apple Silicon (M1/M2/M3) 常见路径
# command script import /opt/homebrew/opt/chisel/libexec/fbchisellldb.py
若你当前使用的是旧版路径 fblldb.py,且能正常加载,可保留;新版本仓库中入口一般为 fbchisellldb.py,以官方 README 为准。
常用命令
在 LLDB 中查看完整命令列表与说明:
(lldb) help
更完整的 Chisel 命令说明与原理见本目录:05-Debug调试@调试器-Chisel LLDB调试工具:从原理到实践。
参考:Chisel - LLDB 命令插件,让调试更 Easy
三、其它工具:Reveal
Reveal 是 iOS 界面调试 的桌面应用,可连接模拟器或真机,实时查看与修改视图层级、约束、属性等,使用上往往比 Xcode 自带的 View Debugging 更流畅、功能更丰富。软件为商业收费,提供试用(如 30 天),试用期过后需购买授权。
集成方式约有多种(Framework、CocoaPods、Swift Package 等),详情见 Reveal 官网集成指南。
四、其它工具:FLEX
FLEX 是 Flipboard 开源的 应用内调试工具集,以第三方库形式集成到 App 中。运行时通过调用 [[FLEXManager sharedManager] showExplorer]; 即可调出调试工具栏,无需连接 Mac,适合真机或脱机场景。
主要功能包括:
- 查看、修改 views
- 查看任意对象的属性
- 动态修改属性
- 动态调用实例方法与类方法
- 查看网络请求过程
- 添加模拟的键盘快捷键
- 查看系统日志
- 从堆中获取任意对象
- 查看沙盒中的文件
- 查看文件系统中的 SQLite / Realm 数据库
- 在模拟器中触发 3D Touch
- 查看应用中所有的类
- 快速获取常用对象(如
[UIApplication sharedApplication]、App Delegate、key window 的 root view controller 等) - 动态查看 NSUserDefaults 中的值