在 Swift 中捕获 NSException
Catching NSException in Swift
Swift 中的以下代码引发 NSInvalidArgumentException 异常:
task = NSTask()
task.launchPath = "/SomeWrongPath"
task.launch()
如何捕获异常?据我了解,Swift 中的 try/catch 是针对 Swift 内抛出的错误,而不是针对从 NSTask(我猜是用 ObjC 编写的)等对象引发的 NSExceptions。我是 Swift 的新手,所以我可能遗漏了一些明显的东西...
编辑:这里是错误的雷达(专门针对 NSTask):openradar.appspot.com/22837476
我的建议是制作一个 C 函数来捕获异常,return 改为 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 的快速简便的方法。
TL;DR: 使用 Carthage 包含 https://github.com/eggheadgames/SwiftTryCatch or CocoaPods to include 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 框架中,然后 运行 测试。我们遇到了未找到 class 函数的问题( 错误:使用未解析的标识符 )。因此,出于这个原因,以及一般的易用性,我们将其打包为 Carthage 库以供一般使用。
奇怪的是,我们可以在其他地方使用 Swift + ObjC 框架,没有任何问题,只是框架的单元测试很困难。
请求 PR! (最好是将它作为 CocoaPod 和 Carthage 的组合构建,并进行一些测试)。
如评论中所述,此 API 为 otherwise-recoverable 失败条件抛出异常是一个错误。 File it, and request an NSError-based alternative. 目前的情况大多是不合时宜的,因为 NSTask
可以追溯到 Apple 标准化仅针对程序员错误的异常之前。
与此同时,虽然您 可以 使用其他答案中的一种机制来捕获 ObjC 中的异常并将它们传递给 Swift,但请注意这样做不是很安全。 ObjC(和 C++)异常背后的 stack-unwinding 机制很脆弱,并且从根本上与 ARC 不兼容。这就是 Apple 仅针对程序员错误使用异常的部分原因——这个想法是您可以(至少在理论上)在开发过程中整理出应用程序中的所有异常情况,并且不会在生产代码中出现异常。 (Swift 错误或 NSError
s,另一方面,可以指示可恢复的情境或用户错误。)
更安全的解决方案是预见可能导致 API 抛出异常的可能条件,并在调用 API 之前处理它们。如果您要索引到 NSArray
,请先检查它的 count
。如果您将 NSTask
上的 launchPath
设置为可能不存在或可能不可执行的内容,请在启动任务之前使用 NSFileManager
进行检查。
这是一些代码,将 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
ObjC.m
#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"
您无法在 Swift 中捕获 Objective-C 异常。但是,您可以通过制作一个 Objective-C 包装器然后导入到 Swift 来解决这个问题。我已经完成了这项工作并使它成为可重复使用的 Swift 包管理器包。只需在 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.
}
版本将上面的答案改进为 return 实际的详细异常消息。
@implementation ObjC
+ (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.tryExecute {
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.}
Swift 中的以下代码引发 NSInvalidArgumentException 异常:
task = NSTask()
task.launchPath = "/SomeWrongPath"
task.launch()
如何捕获异常?据我了解,Swift 中的 try/catch 是针对 Swift 内抛出的错误,而不是针对从 NSTask(我猜是用 ObjC 编写的)等对象引发的 NSExceptions。我是 Swift 的新手,所以我可能遗漏了一些明显的东西...
编辑:这里是错误的雷达(专门针对 NSTask):openradar.appspot.com/22837476
我的建议是制作一个 C 函数来捕获异常,return 改为 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 的快速简便的方法。
TL;DR: 使用 Carthage 包含 https://github.com/eggheadgames/SwiftTryCatch or CocoaPods to include 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 框架中,然后 运行 测试。我们遇到了未找到 class 函数的问题( 错误:使用未解析的标识符 )。因此,出于这个原因,以及一般的易用性,我们将其打包为 Carthage 库以供一般使用。
奇怪的是,我们可以在其他地方使用 Swift + ObjC 框架,没有任何问题,只是框架的单元测试很困难。
请求 PR! (最好是将它作为 CocoaPod 和 Carthage 的组合构建,并进行一些测试)。
如评论中所述,此 API 为 otherwise-recoverable 失败条件抛出异常是一个错误。 File it, and request an NSError-based alternative. 目前的情况大多是不合时宜的,因为 NSTask
可以追溯到 Apple 标准化仅针对程序员错误的异常之前。
与此同时,虽然您 可以 使用其他答案中的一种机制来捕获 ObjC 中的异常并将它们传递给 Swift,但请注意这样做不是很安全。 ObjC(和 C++)异常背后的 stack-unwinding 机制很脆弱,并且从根本上与 ARC 不兼容。这就是 Apple 仅针对程序员错误使用异常的部分原因——这个想法是您可以(至少在理论上)在开发过程中整理出应用程序中的所有异常情况,并且不会在生产代码中出现异常。 (Swift 错误或 NSError
s,另一方面,可以指示可恢复的情境或用户错误。)
更安全的解决方案是预见可能导致 API 抛出异常的可能条件,并在调用 API 之前处理它们。如果您要索引到 NSArray
,请先检查它的 count
。如果您将 NSTask
上的 launchPath
设置为可能不存在或可能不可执行的内容,请在启动任务之前使用 NSFileManager
进行检查。
这是一些代码,将 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
ObjC.m
#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"
您无法在 Swift 中捕获 Objective-C 异常。但是,您可以通过制作一个 Objective-C 包装器然后导入到 Swift 来解决这个问题。我已经完成了这项工作并使它成为可重复使用的 Swift 包管理器包。只需在 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.
}
版本将上面的答案改进为 return 实际的详细异常消息。
@implementation ObjC
+ (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.tryExecute {
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.}