在 Swift 中捕获 NSException
Posted
技术标签:
【中文标题】在 Swift 中捕获 NSException【英文标题】:Catching NSException in Swift 【发布时间】:2015-12-21 21:45:44 【问题描述】:以下 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 异常,例如参见 ***.com/questions/24023112/…。人们可能会认为这是一个错误,即 NSTask 抛出异常而不是返回错误,您可以提交错误报告,但我怀疑 Apple 会更改 API。 感谢@MartinR。我认为这要么是 API 中的错误,要么 Swift 应该提供一种机制来捕获 ObjC 异常(或者两者都更好)......无论如何,我已经打开了一个错误(openradar.appspot.com/22837476),虽然我猜还有更多有同样问题的API方法 我的例子是NSPredicate(fromMetadataQueryString:)
。这应该是一个init?
,所以如果字符串不好,它可能打算返回nil
,但实际上它只是因NSException而崩溃。
好问题!但是如果你自己扔NSException
,那么很容易see how to create NSError
(然后扔那个,而不是NSException
)
【参考方案1】:
这里有一些代码,将 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"
【讨论】:
想通了!你需要在 tryBlock() 成功运行后返回一些东西,否则它总是会碰到 catch 块。我仔细检查了它是否仍然正确触发真正的异常。我将编辑您的答案以包含此更改。 像魅力一样工作。谢谢! 哇,这仍然是在 Swift 2.x 中捕获 NSException 的唯一方法吗?这在 3.0 中有所改变吗? 直到我在@catch
块中添加了明确的 return
之后,它才对我有用;为此进行了编辑(并删除了不必要的let error
,因为错误总是以error
的形式进入一般catch块)
FWIW,在 Swift 3 中,您还可以将 __attribute__((noescape))
添加到 tryBlock
的类型中,这样您就可以在使用 self
时调用方法,而无需显式包含 self
。这使得完整的声明+ (BOOL)catchException:(__attribute__((noescape)) void(^)())tryBlock error:(__autoreleasing NSError **)error;
。【参考方案2】:
我的建议是创建一个 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 的快速简便的方法。
【讨论】:
【参考方案3】: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 框架,只是框架的单元测试遇到了困难。
请求公关! (如果有一个组合 CocoaPod 和 Carthage 构建,并进行一些测试,那就太好了)。
【讨论】:
对于 STC,我建议将文件复制到您的项目中。我在尝试通过包管理器导入和更新 STC 时遇到了太多问题。它有大约 173 种不同的分叉。它们支持 Swift 1、Swift 2 和/或 Swift 3。它们支持 Carthage、CocoaPods 和/或 SPM。它们支持 ios、macOS、tvOS 和/或 watchOS。但是没有一个 fork 支持我需要的东西的特定组合(或者已经在一年内更新),我浪费了太多时间寻找。选择一个,将其复制到您的项目中,然后继续进行更有用的工作。【参考方案4】:如 cmets 中所述,此 API 会针对其他可恢复的故障条件引发异常是一个错误。 File it, and request an NSError-based alternative. 大部分情况下,当前的情况已经过时了,因为 NSTask
可以追溯到 Apple 将异常仅用于程序员错误之前。
与此同时,虽然您可以使用其他答案中的一种机制来捕获 ObjC 中的异常并将它们传递给 Swift,但请注意,这样做并不是很安全。 ObjC(和 C++)异常背后的堆栈展开机制很脆弱,并且从根本上与 ARC 不兼容。这就是为什么 Apple 仅将异常用于程序员错误的部分原因——这个想法是您可以(至少在理论上)在开发过程中整理出您的应用程序中的所有异常情况,并且在您的生产代码中不会发生异常。 (另一方面,Swift 错误或NSError
s 可以指示可恢复的情境或用户错误。)
更安全的解决方案是在调用 API 之前预见可能导致 API 抛出异常并处理它们的可能情况。如果您要索引到 NSArray
,请先检查其 count
。如果您将NSTask
上的launchPath
设置为可能不存在或可能无法执行的内容,请在启动任务之前使用NSFileManager
进行检查。
【讨论】:
我实际上在 9 月份就打开了一个错误(它出现在 cmets 中,我现在将其添加到问题本身)【参考方案5】:该版本改进了上面的答案以返回实际的详细异常消息。
@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.
【讨论】:
【参考方案6】:您无法在 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.
【讨论】:
以上是关于在 Swift 中捕获 NSException的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Swift 中捕获 NSKeyedUnarchiver“NSInvalidUnarchiveOperationException”?
swift没有捕获firebase firestore权限错误,请尝试捕获