在Swift中捕捉NSException


76

Swift中的以下代码引发NSInvalidArgumentException异常:

task = NSTask()
task.launchPath = "/SomeWrongPath"
task.launch()

如何捕获异常?据我了解,Swift中的try / catch是针对Swift中引发的错误,而不是针对NSTask之类的对象引发的NSExceptions(我猜是用ObjC编写的)。我是Swift的新手,所以可能我缺少明显的东西...

编辑:这是该错误的雷达(专门针对NSTask):openradar.appspot.com/22837476


不幸的是,您无法在Swift中捕获Objective-C异常,请参见例如stackoverflow.com/questions/24023112/…。有人可能会认为NSTask引发异常而不是返回错误是一种错误,您可以提交错误报告,但是我怀疑Apple是否会更改API。
马丁R

谢谢@MartinR。我认为这要么是API中的错误,要么是Swift应该提供一种机制来捕获ObjC异常(或者更好的是两者)...无论如何,我已经打开了一个错误(openradar.appspot.com/22837476),尽管我猜还有更多API方法存在相同的问题
silyevsk

对我来说,例子是NSPredicate(fromMetadataQueryString:)。这应该是一个init?,因此如果字符串不正确,则可能打算返回它nil,但实际上,它会因NSException崩溃。
马特

Answers:


138

这是一些将NSExceptions转换为Swift 2错误的代码。

现在您可以使用

do {
    try ObjC.catchException {

       /* calls that might throw an NSException */
    }
}
catch {
    print("An error ocurred: \(error)")
}

ObjC.h:

#import <Foundation/Foundation.h>

@interface ObjC : NSObject

+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error;

@end

对象

#import "ObjC.h"

@implementation ObjC 

+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error {
    @try {
        tryBlock();
        return YES;
    }
    @catch (NSException *exception) {
        *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo];
        return NO;
    }
}

@end

不要忘记将其添加到您的“ * -Bridging-Header.h”中:

#import "ObjC.h"

1
弄清楚了!在tryBlock()成功运行之后,您需要返回一些内容,否则它将始终会触及catch块。我再次检查了它是否仍会在实际异常上正确触发。我将编辑您的答案以包括此更改。
本·巴伦

1
奇迹般有效。谢谢!
Vitalii '16

4
哇,这仍然是在Swift 2.x中捕获NSException的唯一方法吗?3.0版中有更改吗?
valheru

4
直到我return在代码@catch块中添加了一个显式内容,它对我来说才起作用。编辑以做到这一点(并且也删除了不必要的内容let error,因为错误总是以的形式到达一般的catch块中error
matt

3
FWIW,在Swift 3中,您还可以添加__attribute__((noescape))的类型,tryBlock这样您就可以self在使用它时调用方法,而无需self显式包含。这样就可以进行完整的声明+ (BOOL)catchException:(__attribute__((noescape)) void(^)())tryBlock error:(__autoreleasing NSError **)error;
tgaul

12

我建议做一个C函数来捕获异常并返回NSError。然后,使用此功能。

该函数可能如下所示:

NSError *tryCatch(void(^tryBlock)(), NSError *(^convertNSException)(NSException *))
{
    NSError *error = nil;
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        error = convertNSException(exception);
    }
    @finally {
        return error;
    }
}

在一些桥接帮助下,您只需致电:

if let error = tryCatch(task.launch, myConvertFunction) {
    print("An exception happened!", error.localizedDescription)
    // Do stuff
}
// Continue task

注意:我没有真正测试过它,找不到在操场上安装Objective-C和Swift的快捷方法。


9

TL; DR:使用迦太基添加 https://github.com/eggheadgames/SwiftTryCatch 或CocoaPods包含 https://github.com/ravero/SwiftTryCatch

然后,您可以使用这样的代码,而不必担心会崩溃您的应用程序:

import Foundation
import SwiftTryCatch

class SafeArchiver {

    class func unarchiveObjectWithFile(filename: String) -> AnyObject? {

        var data : AnyObject? = nil

        if NSFileManager.defaultManager().fileExistsAtPath(filename) {
            SwiftTryCatch.tryBlock({
                data = NSKeyedUnarchiver.unarchiveObjectWithFile(filename)
                }, catchBlock: { (error) in
                    Logger.logException("SafeArchiver.unarchiveObjectWithFile")
                }, finallyBlock: {
            })
        }
        return data
    }

    class func archiveRootObject(data: AnyObject, toFile : String) -> Bool {
        var result: Bool = false

        SwiftTryCatch.tryBlock({
            result =  NSKeyedArchiver.archiveRootObject(data, toFile: toFile)
            }, catchBlock: { (error) in
                Logger.logException("SafeArchiver.archiveRootObject")
            }, finallyBlock: {
        })
        return result
    }
}

@BPCorp接受的答案按预期工作,但我们发现,如果您尝试将这个Objective C代码合并到大多数Swift框架中,然后运行测试,事情将会变得有些有趣。我们遇到找不到类函数的问题(错误:使用未解析的标识符)。因此,由于这个原因,并且只是易于使用,我们将其打包为通用的迦太基库。

奇怪的是,我们可以在其他地方毫无问题地使用Swift + ObjC框架,这只是该框架的单元测试所困扰。

要求PR!(很高兴有一个组合的CocoaPod和Carthage构建,并进行了一些测试)。


5
对于STC,我建议仅将文件复制到您的项目中。我试图通过程序包管理器导入和更新STC遇到了太多问题。大约有173个不同的分支。它们支持Swift 1,Swift 2和/或Swift3。它们支持Carthage,CocoaPods和/或SPM。它们支持iOS,macOS,tvOS和/或watchOS。但是没有人支持我需要的特殊组合(或者已经更新了一年),而且我浪费了太多时间。选择一个,将其复制到您的项目中,然后继续进行更多有用的工作。
Ssswift

3

如注释中所述,此API会为否则可恢复的故障情况引发异常是一个错误。归档它,并请求基于NSError的替代方法。大多数情况下,当前的情况是过时的,因为NSTask可以追溯到Apple标准化例外规范仅适用于程序员错误之前。

同时,虽然您可以使用其他答案中的一种机制来捕获ObjC中的异常并将其传递给Swift,但是请注意,这样做并不是很安全。ObjC(和C ++)异常背后的堆栈展开机制非常脆弱,并且与ARC基本不兼容。这就是为什么Apple仅将异常用于程序员错误的原因之一-这样的想法是,您可以(理论上至少)在开发过程中对应用程序中的所有异常情况进行分类,并且在生产代码中不发生异常。(NSError另一方面,迅速错误或s可以指示可恢复的情境或用户错误。)

比较安全的解决方案是在调用API之前预见可能导致API引发异常并对其进行处理的可能情况。如果您要建立索引NSArray,请先检查count。如果您将launchPathon设置为NSTask可能不存在或可能无法执行的设置,NSFileManager请在启动任务之前先进行检查。


我实际上在9月就打开了一个bug(它出现在注释中,现在我将其添加到问题本身中)
silyevsk15年

1

您无法在Swift中捕获Objective-C异常。但是,您可以通过制作一个Objective-C包装器来解决此问题,然后将其导入Swift。我已经完成了这项工作,并使其成为可重用的Swift Package Manager软件包。只需在Xcode中添加此程序包,然后像这样使用它:

import Foundation
import ExceptionCatcher

final class Foo: NSObject {}

do {
    let value = try ExceptionCatcher.catch {
        return Foo().value(forKey: "nope")
    }

    print("Value:", value)
} catch {
    print("Error:", error.localizedDescription)
    //=> Error: [valueForUndefinedKey:]: this class is not key value coding-compliant for the key nope.
}

0

传输错误信息的版本稍好一些。

用法示例:

      let c = NSColor(calibratedWhite: 0.5, alpha: 1)
      var r: CGFloat = 0
      var g: CGFloat = 0
      var b: CGFloat = 0
      var a: CGFloat = 0
      do {
         try ObjC.perform {
            c.getRed(&r, green: &g, blue: &b, alpha: &a)
         }
      } catch {
         print(error)
      }

之前:

Error Domain=NSInvalidArgumentException Code=0 "(null)"

后:

Error Domain=NSInvalidArgumentException Code=0 
"*** -getRed:green:blue:alpha: not valid for the NSColor NSCalibratedWhiteColorSpace 0.5 1; need to first convert colorspace." 
UserInfo={NSLocalizedFailureReason=*** -getRed:green:blue:alpha: not valid for the NSColor NSCalibratedWhiteColorSpace 0.5 1; need to first convert colorspace.}

-3

文档中所指定,这是一种简单轻便的方法:

do {
    try fileManager.moveItem(at: fromURL, to: toURL)
} catch let error as NSError {
    print("Error: \(error.domain)")
}

2
这不会帮助,因为在文档的末尾您的链接描述:“在Objective-C处理异常只有<...>在Objective-C,例外是从错误中不同的Objective-C的异常处理使用@try@catch,和@throw语法来指示不可恢复的程序员错误....但是,在Swift中没有从Objective-C异常中恢复安全方法。要处理Objective-C异常,请编写Objective-C代码以在异常到达任何Swift代码之前捕获异常。”
Artem
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.