iOS 異常捕獲涉及 Objective-C/Swift 語言層面、運行時機制及系統底層信號處理,主要分為OC/Swift 異常捕獲、崩潰信號捕獲和自定義異常處理三類,以下從原理、機制到實現細節詳細解析:
一、Objective-C 異常捕獲原理
1. OC 異常的本質
2. @try/@catch 捕獲機制
- 原理:編譯器會將
@try塊代碼標記為 “受保護區域”,@catch塊註冊為異常處理器,@finally塊則確保無論是否捕獲異常都會執行。 - 底層實現:
- 運行時通過
objc_exception_throw函數拋出異常,該函數會遍歷當前線程的調用棧,尋找匹配的@catch處理器; - 若未找到處理器,最終會調用
objc_terminate終止程序(即崩潰)。
- 示例:
objc
@try {
NSArray *array = @[@1, @2];
NSLog(@"%@", array[3]); // 數組越界,拋出 NSRangeException
} @catch (NSException *exception) {
NSLog(@"捕獲異常:%@", exception.reason); // 捕獲並處理
} @finally {
NSLog(@"必執行塊");
}
3. 侷限性
- 僅能捕獲OC 層面的異常(如
NSException),無法捕獲底層崩潰(如野指針、內存訪問錯誤); - Swift 中默認禁用 OC 異常傳播(需手動開啓),且 Swift 錯誤處理優先使用
Error協議而非異常。
二、Swift 錯誤處理與異常捕獲
1. Swift 錯誤處理(Error 協議)
- 遵循
Error協議的枚舉定義錯誤類型; - 通過
throws標記可能拋出錯誤的函數; - 用
do-catch捕獲並處理錯誤。 - 示例:swift
enum NetworkError: Error {
case requestTimeout
case invalidURL
}
func fetchData(url: String) throws {
guard url.starts(with: "https") else {
throw NetworkError.invalidURL
}
}
do {
try fetchData(url: "http://example.com")
} catch let error as NetworkError {
print("網絡錯誤:\(error)")
}
2. Swift 中捕獲 OC 異常
- 方式 1:封裝 OC 異常捕獲方法,Swift 調用:
objc
// OC 工具類:ExceptionCatcher.h
+ (BOOL)catchException:(void(^)(void))tryBlock error:(NSError **)error;
objc
// ExceptionCatcher.m
+ (BOOL)catchException:(void(^)(void))tryBlock error:(NSError **)error {
@try {
tryBlock();
return YES;
} @catch (NSException *exception) {
*error = [NSError errorWithDomain:exception.name code:-1 userInfo:@{NSLocalizedDescriptionKey: exception.reason}];
return NO;
}
}
- Swift 中調用:
swift
var error: NSError?
let success = ExceptionCatcher.catchException({
let array = NSArray(array: [1,2])
_ = array[3] // OC 數組越界
}, error: &error)
三、底層崩潰信號捕獲(Signal)
1. 信號的本質
SIGSEGV:段錯誤(訪問無效內存地址);SIGBUS:總線錯誤(內存對齊錯誤);SIGABRT:程序主動終止(如abort()調用);SIGFPE:浮點運算錯誤;SIGILL:非法指令。
2. 信號捕獲機制
- 通過
signal()或sigaction()函數註冊自定義信號處理器,攔截系統默認信號處理邏輯; - 捕獲信號後,可收集崩潰現場信息(調用棧、寄存器、設備信息),保存日誌或上傳服務器。
- 關鍵限制:
- 信號處理器中不能調用 OC/Swift 方法(運行時可能已損壞),需用純 C 代碼實現;
- 部分信號(如
SIGKILL)無法被捕獲或忽略。
3. 實現示例(捕獲 SIGSEGV)
c
運行
// 導入頭文件
#include <signal.h>
#include <execinfo.h>
void handleSignal(int signal) {
// 收集調用棧
void *callStack[128];
int frames = backtrace(callStack, 128);
char **stackSymbols = backtrace_symbols(callStack, frames);
// 保存日誌(示例:打印到控制枱)
for (int i = 0; i < frames; i++) {
printf("%s\n", stackSymbols[i]);
}
free(stackSymbols);
// 恢復系統默認處理器(避免死循環)
signal(signal, SIG_DFL);
raise(signal); // 讓系統正常終止程序
}
// 註冊信號處理器
void registerSignalHandler() {
signal(SIGSEGV, handleSignal); // 段錯誤
signal(SIGBUS, handleSignal); // 總線錯誤
signal(SIGABRT, handleSignal); // 程序終止
}
四、Mach 異常捕獲(更底層)
1. Mach 異常機制
- 每個線程 / 任務都有異常端口(Exception Port),當發生 Mach 異常(如
EXC_BAD_ACCESS),內核會向異常端口發送消息; - 自定義異常端口可攔截這些消息,實現比信號更早期的異常處理。
2. 實現步驟
- 創建 Mach 端口並註冊為線程 / 任務的異常端口;
- 啓動後台線程監聽異常端口消息;
- 解析異常信息(如異常類型、線程 ID、地址),收集崩潰數據。
五、崩潰捕獲框架原理(以 PLCrashReporter 為例)
六、關鍵注意事項
- OC
@try/@catch僅能捕獲預期異常,無法處理底層崩潰; - 信號 / Mach 異常捕獲需在程序啓動早期註冊(如
main函數開始時),避免遺漏崩潰。
- 信號處理器中不能操作共享資源(如全局變量),需用原子操作或鎖;
- 捕獲崩潰後應儘快終止程序,避免不穩定狀態。
- 崩潰日誌中的地址需通過
dSYM文件和atos工具還原為可讀符號(類名、方法名)。
總結
- OC 層面:用
@try/@catch處理NSException; - Swift 層面:用
do-catch處理Error,通過橋接 OC 捕獲 NSException; - 底層崩潰:用信號處理器捕獲系統信號,通過 Mach 異常捕獲更底層錯誤;
- 實際開發中,推薦使用成熟框架(如 PLCrashReporter)實現全量崩潰監控,同時在業務層用
do-catch/@try/@catch處理預期錯誤。