“线上 iOS 应用又崩溃了,但日志里什么都看不出来。”

这是很多移动端开发团队的日常。洹拓科技在为新乡企业做移动应用开发时,发现一个普遍现象:大家都在用 Crashlytics 或 Bugly,但对崩溃捕获的底层原理理解不深,导致很多问题”靠猜”。

今天从技术角度,拆解 iOS 崩溃捕获的分层方案。

iOS 崩溃的三层结构

iOS 应用的崩溃可以分为三层:

1. Mach 层(内核异常)

最底层的异常,由 CPU 直接触发:

  • EXC_BAD_ACCESS:访问非法内存地址
  • EXC_BAD_INSTRUCTION:执行非法指令
  • EXC_ARITHMETIC:除零等算术错误
  • EXC_CRASH:进程主动终止

这一层的异常最危险,因为应用已经处于不稳定状态。

2. Signal 层(Unix 信号)

Mach 异常会被转换成 Unix 信号:

  • SIGSEGV:段错误(对应 EXC_BAD_ACCESS)
  • SIGBUS:总线错误
  • SIGABRT:主动中止(如 assert 失败)
  • SIGFPE:算术异常

Signal 层是传统 Unix 系统的崩溃处理方式。

3. NSException 层(Objective-C 异常)

最高层的异常,由 Objective-C 运行时抛出:

  • NSRangeException:数组越界
  • NSInvalidArgumentException:参数非法
  • NSInternalInconsistencyException:内部状态不一致

这一层最容易捕获,信息也最完整。

KSCrash 的分层捕获

KSCrash 是一个开源的 iOS 崩溃捕获库,阿里云 RUM SDK 就是基于它构建的。它的核心设计是分层捕获:

Mach 异常捕获

// 注册 Mach 异常处理器
kern_return_t catch_mach_exception_raise(
mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t code_count
) {
// 记录异常信息
// 生成崩溃报告
// 转发给系统默认处理器
}

关键点:Mach 异常处理器运行在独立线程,不能调用不安全函数(如 malloc、objc_msgSend)。

Signal 捕获

// 注册 Signal 处理器
struct sigaction action;
action.sa_sigaction = signal_handler;
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigaction(SIGSEGV, &action, NULL);

关键点:Signal 处理器里只能用 async-signal-safe 函数。

NSException 捕获

NSSetUncaughtExceptionHandler(&exception_handler);

void exception_handler(NSException *exception) {
// 记录异常名称、原因、调用栈
// 生成崩溃报告
}

关键点:NSException 捕获最简单,信息也最完整。

堆栈遍历

崩溃报告的核心是调用栈。iOS 支持两种堆栈遍历:

帧指针遍历(Frame Pointer)

通过 FP 寄存器链接每一帧:

[FP] -> [上一帧FP] -> [更上一帧FP] -> ...

优点:快速、准确
缺点:Release 包通常省略帧指针

DWARF unwind

通过调试信息还原调用栈:

优点:不需要帧指针
缺点:需要解析 DWARF 数据,较慢

KSCrash 两种都支持,优先用帧指针,失败时回退到 DWARF。

僵尸对象检测

“野指针”是 iOS 崩溃最常见的原因。僵尸对象检测的原理:

  1. Hook dealloc 方法
  2. 对象释放后不真正销毁,而是标记为”僵尸”
  3. 再次访问时,检测到僵尸对象,记录调用栈
// 简化版僵尸检测
- (void)dealloc {
// 不真正释放,替换 isa 指针
object_setClass(self, [ZombieClass class]);
}

// 访问僵尸对象时
- (id)forwardingTargetForSelector:(SEL)sel {
// 记录崩溃信息
[ZombieDetector recordZombieAccess:self selector:sel];
return nil;
}

洹拓科技的实践经验

在为新乡企业做 iOS 应用稳定性优化时,我们总结了几条经验:

1. 分层处理

  • Mach 异常:记录最小信息,保证能捕获
  • Signal:补充寄存器状态
  • NSException:记录完整上下文

2. 异步处理

崩溃处理不能阻塞主线程,否则可能导致二次崩溃。KSCrash 用独立线程处理崩溃报告。

3. 数据压缩

崩溃报告通常很大(几百 KB),上传前用 gzip 压缩,减少网络开销。

4. 符号化

崩溃堆栈里的地址需要符号化成函数名。建议:

  • 保留每次构建的 dSYM 文件
  • 用 atos 或 symbolicatecrash 符号化
  • 集成到 CI/CD 自动符号化

洹拓科技的移动开发服务

如果你的 iOS 应用也在为崩溃问题头疼,洹拓科技可以提供:

  • 崩溃分析:诊断崩溃原因,给出修复建议
  • 稳定性优化:从架构到代码,全方位提升稳定性
  • 监控体系:搭建崩溃监控和告警系统

我们为辉县及新乡本地企业提供移动应用开发服务,欢迎联系我们。


新乡洹拓科技有限公司
地址:河南省新乡市辉县市金城百货四楼
电话:400-829-9957
业务:移动开发 | iOS | 稳定性优化 | 软件开发