1. iOS 异常基础
在 iOS 中,异常(Exception)是指程序运行过程中出现的非正常情况,导致应用无法继续正常执行,通常表现为应用崩溃。异常类型从底层到上层包括:
-
Mach 异常 由操作系统内核引发,如访问无效内存地址、除零错误等,属于底层异常机制。
-
BSD Signal 异常 Mach 异常在 BSD 层的表现形式,常见信号有:
SIGSEGV
(段错误)SIGBUS
(总线错误)SIGABRT
(异常终止)等。
-
应用层 Objective-C 异常(NSException) 应用层异常机制,用于运行时异常处理。
-
Swift Error 错误处理机制,处理可恢复错误,不是真正意义上的异常。
2. iOS 异常捕获机制
2.1 Mach 异常
- Mach 异常通过异常端口(exception_port)转换为 BSD Signal。
- 常见 Mach 异常及对应 BSD Signal:
Mach 异常类型 | BSD Signal | 说明 |
---|---|---|
EXC_BAD_ACCESS | SIGSEGV/SIGBUS | 内存访问错误,具体信号取决于访问错误类型 |
EXC_BAD_INSTRUCTION | SIGILL | 非法指令错误 |
EXC_ARITHMETIC | SIGFPE | 算术运算错误 |
EXC_BREAKPOINT | SIGTRAP | 断点异常 |
EXC_SOFTWARE | SIGABRT | 软件触发的异常 |
EXC_GUARD | SIGSYS | 系统调用错误 |
- 某些 Mach 异常只能在 Mach 层处理,如 EXC_RESOURCE、EXC_GUARD、EXC_CORPSE_NOTIFY。
- Mach Exception Handler 通过
task_get_exception_ports
获取异常端口,使用task_set_exception_ports
设置异常处理端口,实现底层异常处理。
arduino
kern_return_t task_set_exception_ports(
task_t task,
exception_mask_t exception_mask,
mach_port_t exception_port,
exception_behavior_t behavior,
thread_state_flavor_t flavor
);
- 异常处理流程包括获取异常信息、线程状态、记录现场和资源清理。
2.2 BSD Signal
- 通过
signal_handler()
注册信号处理器。 - 示例代码:
arduino
#include <signal.h>
void SignalHandler(int signal) {
switch (signal) {
case SIGSEGV: break;
case SIGBUS: break;
case SIGABRT: break;
case SIGILL: break;
case SIGFPE: break;
default: break;
}
NSString *callStack = [NSThread callStackSymbols].description;
NSLog(@"调用栈信息:\n%@", callStack);
exit(signal);
}
void RegisterSignalHandler(void) {
signal(SIGSEGV, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGFPE, SignalHandler);
}
- BSD Signal 与 NSException 是独立机制,Signal 不会自动转换为 NSException。
2.3 应用层异常处理
Objective-C 异常处理
- 使用
@try-@catch-@finally
语法:
scss
@try {
NSArray *array = @[];
id obj = array[1]; // 越界异常
} @catch (NSException *exception) {
NSLog(@"捕获到异常:%@", exception);
NSLog(@"异常名称:%@", exception.name);
NSLog(@"异常原因:%@", exception.reason);
NSLog(@"异常调用栈:%@", exception.callStackSymbols);
} @finally {
// 无论是否发生异常,这里都会执行
NSLog(@"执行清理工作");
}
- 注册全局未捕获异常处理器:
scss
static void UncaughtExceptionHandler(NSException *exception) {
NSString *exceptionInfo = [NSString stringWithFormat: @"异常名称:%@\n异常原因:%@\n异常堆栈:%@", [exception name], [exception reason], [exception callStackSymbols]];
NSLog(@"%@", exceptionInfo);
}
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
Swift 错误处理
- 使用
Error
协议和do-catch
语法:
swift
enum NetworkError: Error {
case badURL
case noData
case parseError
}
func fetchData(from urlString: String) throws -> Data {
guard let url = URL(string: urlString) else { throw NetworkError.badURL }
guard let data = try? Data(contentsOf: url) else { throw NetworkError.noData }
return data
}
do {
let data = try fetchData(from: "https://xxx.xx.com")
// 处理数据
} catch NetworkError.badURL {
print("无效的 URL")
} catch NetworkError.noData {
print("没有数据")
} catch {
print("其他错误:(error)")
}
3. 异常信息的处理和分析
3.1 关键信息提取
- 收集异常类型、原因、时间、线程信息。
- 记录设备型号、iOS 版本、App 版本、是否越狱等环境信息。
- 采集调用栈、内存使用、CPU 占用、系统日志等运行时状态。
3.2 堆栈符号化
-
概念:将崩溃时的内存地址转换为函数名、文件名、行号。
-
dSYM 文件结构:
- 符号表(Symbol Table):函数名、地址映射。
- 调试信息(DWARF):源代码文件名、行号等。
-
符号化流程:
- 地址解析:提取崩溃日志中的内存地址。
- 符号查找:在 dSYM 中定位符号。
- 调试信息解析:获取文件名和行号。
- 信息整合:生成可读堆栈信息。
-
工具示例:
atos
、symbolicatecrash
。
3.3 上下文信息获取
- 内存信息:应用内存用量、系统可用内存、内存警告。
- 线程信息:线程状态、优先级、依赖关系。
- 系统信息:系统负载、关键日志、其他进程状态。
- 用户行为:操作路径、行为日志、网络请求记录。
通过系统化信息收集和关联,开发者能准确定位崩溃环境和触发条件,复现问题,分析根因,制定修复方案。
iOS 异常捕获流程图
flowchart TD
A[异常发生] --> B{Mach 异常?}
B -- 是 --> C[Mach Exception Handler 处理]
C --> D{处理成功?}
D -- 是 --> E[异常处理完成]
D -- 否 --> F[转换为 BSD Signal]
B -- 否 --> F
F --> G[信号处理函数 Signal Handler]
G --> H{处理成功?}
H -- 是 --> E
H -- 否 --> I[触发应用层 Objective-C 异常 NSException]
I --> J[try-catch 捕获异常]
J --> K{捕获成功?}
K -- 是 --> E
K -- 否 --> L[调用 NSSetUncaughtExceptionHandler]
L --> E
E --> M[异常信息收集与记录]
M --> N[堆栈符号化]
N --> O[异常分析与定位]
O --> P[修复与优化]
总结
iOS 异常捕获机制涵盖从底层 Mach 异常、BSD Signal 到应用层 Objective-C 和 Swift 的错误处理。异常发生后,系统通过多层机制捕获异常,收集丰富的环境和运行时信息,结合堆栈符号化技术,帮助开发者快速定位和修复问题,保障应用的稳定性和用户体验。