You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift实现iOS简易崩溃检测:信号处理器未触发求排查

iOS简易崩溃检测实现问题:信号处理器未被调用

我正尝试实现简易的iOS崩溃检测功能。十年前用Objective-C借助UncaughtExceptionHandler就能轻松搞定,但Swift需要更多处理,我不确定当前思路是否正确。

我参考其他方案和评论,尝试了以下基于信号的实现,却发现处理器并未被调用。请问我遗漏了什么或哪里操作有误?

import Foundation

class CrashDetection: NSObject {
    private static let observedSignals = [SIGABRT, SIGILL, SIGSEGV, SIGFPE, SIGBUS, SIGPIPE, SIGTRAP]
    private static var previousSignalHandlers: [Int32:(@convention(c) (Int32) -> Void)] = [:]

    @objc class func start() {
        NSSetUncaughtExceptionHandler(CrashDetection.recieveException)
        observedSignals.forEach { signalType in
            let oldHandler = signal(signalType, CrashDetection.recieveSignal)
            CrashDetection.previousSignalHandlers[signalType] = oldHandler
        }
    }

    private static let recieveException: @convention(c) (NSException) -> Swift.Void = {
        (recieveException) -> Void in
        UserDefaults.standard.setValue(true, forKey: "crashDidOccur")
    }

    private static let recieveSignal: @convention(c) (Int32) -> Void = {
        (recievedSignal) -> Void in
        UserDefaults.standard.setValue(true, forKey: "crashDidOccur")
        NSSetUncaughtExceptionHandler(nil)
        observedSignals.forEach { signalType in
            signal(signalType, previousSignalHandlers[signalType])
        }
    }

}

问题分析与解决方案

你的实现存在几个关键问题,导致信号处理器未被正确触发:

  1. 信号处理流程错误
    在recieveSignal中,你直接恢复了所有信号的旧处理器,但没有重新发送当前接收到的信号。系统在触发信号时会调用当前注册的处理器,你恢复旧处理器后没有让系统继续处理该信号,导致流程中断,同时你的逻辑也可能因为进程即将终止而无法完全执行。

  2. UserDefaults写入不可靠
    崩溃发生时,App进程可能很快被系统终止,UserDefaults的自动同步可能来不及完成,导致标记无法持久化。必须手动调用synchronize()强制写入(尽管iOS 10+官方不推荐,但崩溃场景下是必要的)。

  3. 未覆盖Swift运行时崩溃
    Swift的很多崩溃(如数组越界、强制解包nil)属于Mach异常,不会触发传统的POSIX信号,也无法被NSSetUncaughtExceptionHandler捕获,必须额外注册Mach异常处理。

  4. 函数名拼写错误
    recieveException和recieveSignal的正确拼写是receive,虽然这不会直接导致处理器失效,但属于代码规范问题,可能引发后续维护隐患。

修正后的代码

import Foundation
import MachO

class CrashDetection: NSObject {
    private static let observedSignals = [SIGABRT, SIGILL, SIGSEGV, SIGFPE, SIGBUS, SIGPIPE, SIGTRAP]
    private static var previousSignalHandlers: [Int32: (@convention(c) (Int32) -> Void)?] = [:]
    private static var machExceptionPort: mach_port_t = 0

    @objc class func start() {
        // 捕获Objective-C异常
        NSSetUncaughtExceptionHandler(receiveException)
        
        // 注册信号处理器
        observedSignals.forEach { signalType in
            let oldHandler = signal(signalType, receiveSignal)
            previousSignalHandlers[signalType] = oldHandler
        }
        
        // 注册Mach异常处理器,覆盖Swift运行时崩溃
        registerMachExceptionHandler()
    }

    private static let receiveException: @convention(c) (NSException) -> Void = { exception in
        // 记录崩溃标记并强制同步
        UserDefaults.standard.setValue(true, forKey: "crashDidOccur")
        UserDefaults.standard.synchronize()
        
        // 恢复原异常处理器并重新抛出异常
        NSSetUncaughtExceptionHandler(nil)
        exception.raise()
    }

    private static let receiveSignal: @convention(c) (Int32) -> Void = { signal in
        // 记录崩溃标记并强制同步
        UserDefaults.standard.setValue(true, forKey: "crashDidOccur")
        UserDefaults.standard.synchronize()
        
        // 恢复当前信号的原处理器
        if let oldHandler = previousSignalHandlers[signal] {
            signal(signal, oldHandler)
        } else {
            signal(signal, SIG_DFL)
        }
        
        // 重新发送信号,让系统完成崩溃流程
        raise(signal)
    }
    
    private static func registerMachExceptionHandler() {
        // 创建异常接收端口
        let port = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE)
        mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND)
        
        // 定义异常处理闭包
        let exceptionHandler: exception_handler_t = { (_, _, _, _, _, _) -> kern_return_t in
            UserDefaults.standard.setValue(true, forKey: "crashDidOccur")
            UserDefaults.standard.synchronize()
            mach_port_deallocate(mach_task_self(), port)
            return KERN_FAILURE
        }
        
        // 设置异常监听端口
        exception_set_port(mach_task_self(), EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION, port)
        machExceptionPort = port
        
        // 启动后台线程监听Mach异常
        Thread.detachNewThreadSelector(#selector(listenMachExceptions), toTarget: self, with: nil)
    }
    
    @objc private static func listenMachExceptions() {
        var message = mach_msg_header_t()
        while true {
            let result = mach_msg(&message, MACH_RCV_MSG, 0, MemoryLayout<mach_msg_header_t>.size, machExceptionPort, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL)
            if result != KERN_SUCCESS {
                break
            }
        }
    }
}

关键改进说明

  • 新增Mach异常处理逻辑,能够捕获Swift特有的运行时崩溃;
  • 调整信号处理流程,先完成标记写入,再恢复原处理器并重新发送信号,确保系统能正常完成崩溃流程;
  • 加入UserDefaults.standard.synchronize(),保证崩溃标记能被持久化;
  • 修正函数名拼写错误,提升代码规范性。

内容的提问来源于stack exchange,提问作者Helge Becker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 13:27:07