在Swift中如何使用NSSetUncaughtExceptionHandler?

23

在Objective-C中,我调用NSSetUncaughtExceptionHandler(&exceptionHandler)方法来记录异常。在Swift中该如何调用?

4个回答

53

从Swift 2(Xcode 7)开始,您可以将Swift函数/闭包传递给接受C函数指针的参数。来自Xcode 7发布说明:

  

C函数指针的本地支持:使用闭包或全局函数调用带有函数指针参数的C函数,但限制是该闭包不能捕获其本地上下文。

因此,以下代码将编译并运行:

func exceptionHandler(exception : NSException) {
    print(exception)
    print(exception.callStackSymbols)
}

NSSetUncaughtExceptionHandler(exceptionHandler)

或者使用“内联”闭包:

NSSetUncaughtExceptionHandler { exception in
    print(exception)
    print(exception.callStackSymbols)
}

这段代码与相应的 Objective-C 代码完全相同:它捕获除了未被捕获的 NSException 之外的所有异常。 因此,以下异常将被捕获:

let array = NSArray()
let elem = array.objectAtIndex(99)

注意:它不会捕获任何Swift 2错误(来自throw)或Swift运行时错误,因此不会捕获以下内容:

let arr = [1, 2, 3]
let elem = arr[4]

3
哦!我没有注意到那个...那么有什么方法可以捕获Swift运行时错误吗? - Durai Amuthan.H
1
@MobileMon 这可能意味着你的 exceptionHandler 是某个类的方法,而不是全局函数。 - Martin R
1
太棒了,非常感谢!感谢您以如此友善、支持的方式分享您的知识。许多声望高的同行缺乏您拥有的同样的善良。再次感谢! - Crashalot
2
如果您想要更漂亮的堆栈跟踪,请使用 exception.callStackSymbols.joinWithSeparator("\n") - Pavel Alexeev
2
有人能解释一下在哪里使用这段代码吗?我把它放在appdelegate的didFinishLaunchingWithOptions方法中,但它不起作用。 - toing_toing
显示剩余13条评论

13

更新

在Swift 2中,你可以将Swift函数和闭包传递作为C函数指针。请查看下面Martin R的回答.

原始回答

在Xcode 6 beta 6中,你不能这样做。

Swift确实支持传递函数指针,但它们几乎像不透明指针一样处理。你既不能定义一个C函数指针来调用Swift函数,也不能在Swift中调用C函数指针。

这意味着你可以从Swift中调用NSSetUncaughtExceptionHandler(),但必须在Objective-C中实现处理程序。你需要这样的头文件:

volatile void exceptionHandler(NSException *exception);
extern NSUncaughtExceptionHandler *exceptionHandlerPtr;

在实现中,你需要像这样做:

volatile void exceptionHandler(NSException *exception) {
    // Do stuff
}
NSUncaughtExceptionHandler *exceptionHandlerPtr = &exceptionHandler;

在导入Swift桥接头文件之后,您可以像往常一样设置异常处理程序:

NSSetUncaughtExceptionHandler(exceptionHandlerPtr)

非常感谢!我能够通过您的建议输出NSLog :) - billy
@jou 我正在按照你的方法进行。我将 | volatile void exceptionHandler(NSException *exception); extern NSUncaughtExceptionHandler *exceptionHandlerPtr; | 粘贴到我的 Bridging-Header.h 文件中,并在我的 Bridging-Header.m 文件中添加了这个 | volatile void exceptionHandler(NSException *exception) { // Do stuff } NSUncaughtExceptionHandler *exceptionHandlerPtr = &exceptionHander; | 并从 Swift appDelegate 的方法 | didFinishLaunchingWithOptions | 中调用,像这样 NSSetUncaughtExceptionHandler(exceptionHandlerPtr),但是...继续下一个评论。 - Qadir Hussain
但我遇到了错误“Undefined symbols for architecture i386”: “_exceptionHandlerPtr”,引用自: __TFC25Home_Automation_Sys_Swift11AppDelegate11applicationfS0_FTCSo13UIApplication29didFinishLaunchingWithOptionsGSqGVSs10DictionaryCSo8NSObjectPSs9AnyObject____Sb in AppDelegate.o ld:找不到符号(s)的i386架构 - Qadir Hussain
@billy,你能帮我解决这个问题吗?你是怎么实现的?请看一下我上面的评论。 - Qadir Hussain
@QadirHussain 你需要在一个独立的.h/.m文件对异常处理器进行实现,然后将其头文件导入到桥接头文件中。 - jou
所以,原来的答案在Swift 2.2之后就不再适用了吗? - Slavcho

7

Swift 5:

1. 将此方法添加到AppDelegate中:

    func storeStackTrace() {
        NSSetUncaughtExceptionHandler { exception in
            print(exception)
            // do stuff with the exception
        }
     }

2. 在didFinishLaunchingWithOptions方法中调用此方法,并在立即引发异常

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            storeStackTrace()

            let exception = NSException(name: NSExceptionName(rawValue: "arbitrary"), reason: "arbitrary reason", userInfo: nil)
            exception.raise()


    }

3. 跟踪控制台输出,它会立即打印出你刚刚提供的异常原因开始的异常信息。


它对我来说崩溃了。 *** 由于未捕获的异常“任意”,原因:“任意原因”而终止应用程序 *** 第一个抛出调用堆栈: - Dhyaan
2
嗨@Dhyaan,好的,它应该崩溃,这是预期的 :) 你正在故意通过exception.raise()引起崩溃。目的是在上面的storeStackTrace()方法中跟踪异常。 - Milos Dimic

5
重新打开后续应用程序时,您可能会看到错误。
这是针对 Swift 4 的代码。在 didFinishLaunchingWithOptions() 中添加以下内容:
NSSetUncaughtExceptionHandler { exception in
            print("Error Handling: ", exception)
            print("Error Handling callStackSymbols: ", exception.callStackSymbols)

            UserDefaults.standard.set(exception.callStackSymbols, forKey: "ExceptionHandler")
            UserDefaults.standard.synchronize()
        }

将以下代码添加到firstViewController的viewLoad()函数中:

// ERROR ExceptionHandler
        if let exception = UserDefaults.standard.object(forKey: "ExceptionHandler") as? [String] {

            print("Error was occured on previous session! \n", exception, "\n\n-------------------------")
            var exceptions = ""
            for e in exception {
                exceptions = exceptions + e + "\n"
            }
            AlertFunctions.messageType.showYesNoAlert("Error was occured on previous session!", bodyMessage: exceptions, {

            }, no: {
                UserDefaults.standard.removeObject(forKey: "ExceptionHandler")
                UserDefaults.standard.synchronize()
            })
        }

编辑:代码可行。但是在出现错误后需要重新打开应用程序。


1
我已经在Swift中添加了代码,但异常处理程序不起作用。您能否请教我原因? - Anjan
为 iPhone 应用程序设计的崩溃报告器 [第一部分] 和 [第二部分]。 - Yunus T.
亲爱的Yunus, 我正在尝试同样的事情,但是我遇到了这个错误:“无法从捕获上下文的闭包中形成C函数指针”,我是否漏掉了什么? - Niloufar
嗨,代码可以运行。但是在出现错误后,您需要重新打开应用程序。 - Yunus T.
请问您能帮我理解如何使用它来跟踪像“数组越界”这样的崩溃吗? - Karan Bhatia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接