iOS 異常捕獲涉及 Objective-C/Swift 語言層面、運行時機制及系統底層信號處理,主要分為OC/Swift 異常捕獲崩潰信號捕獲自定義異常處理三類,以下從原理、機制到實現細節詳細解析:

一、Objective-C 異常捕獲原理

1. OC 異常的本質

OC 異常基於 NSException 類實現,本質是運行時拋出的對象,通過 @throw 主動拋出或系統自動觸發(如數組越界、未識別的 selector)。異常拋出後會中斷當前代碼執行流程,沿調用棧向上傳播,直到被 @try/@catch 捕獲。

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 協議)

Swift 不推薦使用異常,而是通過錯誤拋出(throw)- 捕獲(do-catch 機制處理預期錯誤,本質是類型安全的錯誤傳遞

  • 遵循 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 異常

Swift 默認不處理 OC 拋出的 NSException,需通過橋接 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)

iOS 中大部分崩潰(如野指針、內存錯誤、CPU 異常)屬於底層信號,需通過信號處理器(Signal Handler) 捕獲,原理如下:

1. 信號的本質

信號是操作系統向進程發送的中斷通知,表示發生了異常事件(如內存訪問錯誤對應 SIGSEGV,非法指令對應 SIGILL)。iOS 中常見崩潰信號:

  • 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 異常捕獲(更底層)

iOS 基於 Mach 內核,信號本質是 Mach 異常的 “上層封裝”。通過註冊Mach 異常端口,可在信號觸發前捕獲更底層的異常:

1. Mach 異常機制

  • 每個線程 / 任務都有異常端口(Exception Port),當發生 Mach 異常(如 EXC_BAD_ACCESS),內核會向異常端口發送消息;
  • 自定義異常端口可攔截這些消息,實現比信號更早期的異常處理。

2. 實現步驟

  • 創建 Mach 端口並註冊為線程 / 任務的異常端口;
  • 啓動後台線程監聽異常端口消息;
  • 解析異常信息(如異常類型、線程 ID、地址),收集崩潰數據。

注意:Mach 異常捕獲需處理內核級消息交互,實現複雜,通常用於專業崩潰監控框架(如 Crashlytics、Bugly)。

五、崩潰捕獲框架原理(以 PLCrashReporter 為例)

主流崩潰監控框架(如 PLCrashReporter、KSCrash)整合了信號捕獲Mach 異常捕獲OC 異常捕獲,核心流程:

  1. 啓動時註冊信號處理器和 Mach 異常端口;
  2. 異常發生時,暫停所有線程(避免數據競爭);
  3. 通過 backtrace()/dladdr() 解析調用棧,還原符號(類名、方法名);
  4. 將崩潰信息(調用棧、設備信息、系統版本)保存到本地日誌;
  5. 下次啓動時上傳日誌到服務器。

六、關鍵注意事項

  1. 異常捕獲的時效性
  • OC @try/@catch 僅能捕獲預期異常,無法處理底層崩潰;
  • 信號 / Mach 異常捕獲需在程序啓動早期註冊(如 main 函數開始時),避免遺漏崩潰。
  1. 線程安全
  • 信號處理器中不能操作共享資源(如全局變量),需用原子操作或鎖;
  • 捕獲崩潰後應儘快終止程序,避免不穩定狀態。
  1. 符號還原
  • 崩潰日誌中的地址需通過 dSYM 文件和 atos 工具還原為可讀符號(類名、方法名)。

總結

iOS 異常捕獲需分層處理:

  • OC 層面:用 @try/@catch 處理 NSException
  • Swift 層面:用 do-catch 處理 Error,通過橋接 OC 捕獲 NSException;
  • 底層崩潰:用信號處理器捕獲系統信號,通過 Mach 異常捕獲更底層錯誤;
  • 實際開發中,推薦使用成熟框架(如 PLCrashReporter)實現全量崩潰監控,同時在業務層用 do-catch/@try/@catch 處理預期錯誤。