在Swift中捕获NSException

时间:2015-09-24 10:16:32

标签: swift cocoa-touch exception foundation nsexception

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

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

如何捕捉异常?据我所知,Swift中的try / catch是针对Swift中抛出的错误,而不是针对像NSTask这样的对象引发的NSExceptions(我猜这是用ObjC编写的)。我是Swift的新手,所以我可能会错过一些明显的东西......

编辑:这里有一个针对该漏洞的雷达(专门针对NSTask):openradar.appspot.com/22837476

7 个答案:

答案 0 :(得分:110)

以下是一些将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(^)())tryBlock error:(__autoreleasing NSError **)error;

@end

ObjC.m

#import "ObjC.h"

@implementation ObjC 

+ (BOOL)catchException:(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 :(得分: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

注意:我没有真正测试它,我找不到一个快速简便的方法在Playground中使用Objective-C和Swift。

答案 2 :(得分:9)

TL; DR:使用Carthage包含 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框架中然后运行测试,那么事情会变得有趣。我们遇到了无法找到类函数的问题(错误:使用未解析的标识符)。因此,由于这个原因,以及一般的易用性,我们将其打包为一般用途的Carthage库。

奇怪的是,我们可以在其他地方使用Swift + ObjC框架,没有任何问题,只是框架的单元测试正在挣扎。

要求PR! (让它成为一个组合CocoaPod和迦太基的构建,并进行一些测试会很好。)

答案 3 :(得分:3)

如评论中所述,此API会针对其他可恢复的失败条件抛出异常,这是一个错误。 File it, and request an NSError-based alternative.大多数情况下,目前的状况是不合时宜的,因为NSTask可以追溯到Apple标准化的例外仅针对程序员错误。

与此同时,当你可以使用其他答案中的一种机制来捕获ObjC中的异常并将它们传递给Swift时,请注意这样做并不是非常安全。 ObjC(和C ++)异常背后的堆栈展开机制是脆弱的,并且与ARC基本上不兼容。这就是为什么Apple仅为程序员错误使用异常的原因之一 - 这个想法是你可以(理论上至少)在开发过程中解决应用程序中的所有异常情况,并且在生产代码中不会出现异常。 (另一方面,Swift错误或NSError可表示可恢复的情境或用户错误。)

更安全的解决方案是预见在调用API之前可能导致API抛出异常并处理它们的可能情况。如果您要编入NSArray索引,请先检查其count。如果您将launchPath上的NSTask设置为可能不存在或可能无法执行的内容,请在启动任务之前使用NSFileManager进行检查。

答案 4 :(得分:1)

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

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.
}

答案 5 :(得分:0)

根据documentation中的说明,这是一种简单且轻巧的方法:

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

答案 6 :(得分:0)

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

.Include(x => x.Stores)

用法示例:

var res = _db.Cities.Where(x => x.Id == id)
                .Include(x => x.Parks)  // this works fine           
                .FirstOrDefault();

之前:

@implementation ObjCExceptionHandler

+ (BOOL)tryExecute:(nonnull void(NS_NOESCAPE^)(void))tryBlock error:(__autoreleasing NSError * _Nullable * _Nullable)error {
   @try {
      tryBlock();
      return YES;
   }
   @catch (NSException *exception) {
      NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
      if (exception.userInfo != NULL) {
         userInfo = [[NSMutableDictionary alloc] initWithDictionary:exception.userInfo];
      }
      if (exception.reason != nil) {
         if (![userInfo.allKeys containsObject:NSLocalizedFailureReasonErrorKey]) {
            [userInfo setObject:exception.reason forKey:NSLocalizedFailureReasonErrorKey];
         }
      }
      *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:userInfo];
      return NO;
   }
}

@end

之后:

      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)
      }