捕获 Objective-C 和 Swift 异常
Catching Both Objective-C and Swift Exceptions
实际上我没有问题。但是我 运行 遇到了一个问题,即在 Swift 中使用单个 catch-block 处理 Swift 错误和 Objective-C 类型 NSException
异常,类似于这个:
do {
try object1.throwingObjectiveCMethod()
try object2.throwingSwiftMethod()
} catch {
print("Error:", error)
}
我找不到这个问题的答案,所以我想我会 post 在这里,以防其他人 运行 进入它。
退一步说,我需要在 Swift 中使用一些旧的 Objective-C 库,这可能会抛出需要在 Swift.[=33= 中捕获的 NSExceptions ]
假设这个库看起来有点像这样:
#import <Foundation/Foundation.h>
@interface MyLibrary : NSObject
- (void)performRiskyTask;
@end
@implementation MyLibrary
- (void)performRiskyTask {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"Something went wrong"
userInfo:nil];
}
@end
现在,假设我尝试按以下方式使用此库:
do {
let myLibrary = MyLibrary()
try myLibrary.performRiskyTask()
} catch {
print("Error caught:", error)
}
Swift 编译器已经让我知道 try
表达式中没有抛出函数,并且 catch
块是不可访问的。事实上,当我 运行 代码时,它崩溃并出现 运行 时间错误:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Something went wrong'
*** First throw call stack:
(
0 CoreFoundation 0x00007ff8099861e3 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007ff8096e6c13 objc_exception_throw + 48
2 Exceptions 0x0000000100003253 -[MyLibrary performRiskyTask] + 83
3 Exceptions 0x00000001000035c2 main + 66
4 dyld 0x000000010001951e start + 462
)
libc++abi: terminating with uncaught exception of type NSException
正如 Swift 文档中所述,只有 NSError **
模式被 t运行 指定为 Swift 的 try-catch 模式。没有 built-in 方法来捕获 NSExceptions:
In Swift, you can recover from errors passed using Cocoa’s error pattern, as described above in Catch Errors. However, there’s no safe way to recover from Objective-C exceptions in Swift. To handle Objective-C exceptions, write Objective-C code that catches exceptions before they reach any Swift code.
(https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift)
正如另一个答案中所指出的(参见 ),可以使用以下通用异常处理程序解决此问题:
#import <Foundation/Foundation.h>
@interface ObjC : NSObject
+ (BOOL)catchException:(void (^)(void))tryBlock error:(NSError **)error;
@end
@implementation ObjC
+ (BOOL)catchException:(void (^)(void))tryBlock error:(NSError **)error {
@try {
tryBlock();
return YES;
} @catch (NSException *exception) {
if (error != NULL) {
*error = [NSError errorWithDomain:exception.name code:-1 userInfo:@{
NSUnderlyingErrorKey: exception,
NSLocalizedDescriptionKey: exception.reason,
@"CallStackSymbols": exception.callStackSymbols
}];
}
return NO;
}
}
@end
重写我的 Swift 代码(并在我的桥接 header 中导入 ObjC.h
)…
do {
let myLibrary = MyLibrary()
try ObjC.catchException {
myLibrary.performRiskyTask()
}
} catch {
print("Error caught:", error)
}
…,正在捕获异常:
Error caught: Error Domain=NSInternalInconsistencyException Code=-1 "Something went wrong" UserInfo={CallStackSymbols=(
0 CoreFoundation 0x00007ff8099861e3 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007ff8096e6c13 objc_exception_throw + 48
2 Exceptions 0x0000000100002c33 -[MyLibrary performRiskyTask] + 83
3 Exceptions 0x0000000100003350 $s10ExceptionsyycfU_ + 32
4 Exceptions 0x00000001000033d8 $sIeg_IeyB_TR + 40
5 Exceptions 0x0000000100002c9f +[ObjC catchException:error:] + 95
6 Exceptions 0x0000000100003069 main + 265
7 dyld 0x000000010001d51e start + 462
), NSLocalizedDescription=Something went wrong, NSUnderlyingError=Something went wrong}
Program ended with exit code: 0
所以这很好用。
但现在让我们说,我的库调用嵌套在其他 Swift 函数中,可能会抛出其他 Swift-native 错误:
func performTasks() throws {
try performOtherTask()
useLibrary()
}
func performOtherTask() throws {
throw MyError.someError(message: "Some other error has occurred.")
}
func useLibrary() {
let myLibrary = MyLibrary()
myLibrary.performRiskyTask()
}
enum MyError: Error {
case someError(message: String)
}
当然,我可以用 ObjC.catchException
块包围对我的库的每次调用,甚至可以围绕这个库构建一个 Swift 包装器。但相反,我想在一个地方捕获所有异常,而不必为每个方法调用编写额外的代码:
do {
try ObjC.catchException {
try performTasks()
}
} catch {
print("Error caught:", error)
}
但是,此代码无法编译,因为传递给 ObjC.catchException
方法的闭包不应抛出:
Invalid conversion from throwing function of type '() throws -> ()' to non-throwing function type '() -> Void'
要解决此问题,请将 void (^tryBlock)(void)
替换为 non-escaping void (^tryBlock)(NSError **)
并将 Objective-C 方法标记为“为 Swift 优化”(类似至 ):
#import <Foundation/Foundation.h>
@interface ObjC : NSObject
+ (BOOL)catchException:(void (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error NS_REFINED_FOR_SWIFT;
@end
然后将错误指针传递给tryBlock
并根据是否发生错误更改return值:
@implementation ObjC
+ (BOOL)catchException:(void (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error {
@try {
tryBlock(error);
return error == NULL || *error == nil;
} @catch (NSException *exception) {
if (error != NULL) {
*error = [NSError errorWithDomain:exception.name code:-1 userInfo:@{
NSUnderlyingErrorKey: exception,
NSLocalizedDescriptionKey: exception.reason,
@"CallStackSymbols": exception.callStackSymbols
}];
}
return NO;
}
}
@end
然后使用以下扩展“优化”此方法:
extension ObjC {
static func catchException(_ block: () throws -> Void) throws {
try __catchException { (errorPointer: NSErrorPointer) in
do {
try block()
} catch {
errorPointer?.pointee = error as NSError
}
}
}
}
现在,Swift 代码可以编译:
do {
try ObjC.catchException {
try performTasks()
}
print("No errors.")
} catch {
print("Error caught:", error)
}
它捕获了 Swift 错误:
Error caught: someError(message: "Some other error has occurred.")
Program ended with exit code: 0
同样,它也会捕获NSException
:
func performOtherTask() throws {
//throw MyError.someError(message: "Some other error has occurred.")
}
Error caught: Error Domain=NSInternalInconsistencyException Code=-1 "Something went wrong" UserInfo={CallStackSymbols=(
0 CoreFoundation 0x00007ff8099861e3 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007ff8096e6c13 objc_exception_throw + 48
2 Exceptions 0x0000000100002643 -[MyLibrary performRiskyTask] + 83
3 Exceptions 0x00000001000030fa $s10Exceptions10useLibraryyyF + 58
4 Exceptions 0x0000000100002cfa $s10Exceptions12performTasksyyKF + 42
5 Exceptions 0x0000000100002c9f $s10ExceptionsyyKXEfU_ + 15
6 Exceptions 0x00000001000031b8 $sSo4ObjCC10ExceptionsE14catchExceptionyyyyKXEKFZySAySo7NSErrorCSgGSgXEfU_ + 56
7 Exceptions 0x000000010000339c $sSAySo7NSErrorCSgGSgIgy_AEIegy_TR + 12
8 Exceptions 0x000000010000340c $sSAySo7NSErrorCSgGSgIegy_AEIyBy_TR + 28
9 Exceptions 0x00000001000026b3 +[ObjC catchException:error:] + 99
10 Exceptions 0x0000000100002e93 $sSo4ObjCC10ExceptionsE14catchExceptionyyyyKXEKFZ + 371
11 Exceptions 0x00000001000029ce main + 46
12 dyld 0x000000010001d51e start + 462
), NSLocalizedDescription=Something went wrong, NSUnderlyingError=Something went wrong}
Program ended with exit code: 0
而且,当然,如果根本没有抛出异常,它将 运行 通过:
- (void)performRiskyTask {
// @throw [NSException exceptionWithName:NSInternalInconsistencyException
// reason:@"Something went wrong"
// userInfo:nil];
}
No errors.
Program ended with exit code: 0
编辑: 要支持抛出 NSExceptions 的 non-void 方法,请参阅 this post。
实际上我没有问题。但是我 运行 遇到了一个问题,即在 Swift 中使用单个 catch-block 处理 Swift 错误和 Objective-C 类型 NSException
异常,类似于这个:
do {
try object1.throwingObjectiveCMethod()
try object2.throwingSwiftMethod()
} catch {
print("Error:", error)
}
我找不到这个问题的答案,所以我想我会 post 在这里,以防其他人 运行 进入它。
退一步说,我需要在 Swift 中使用一些旧的 Objective-C 库,这可能会抛出需要在 Swift.[=33= 中捕获的 NSExceptions ]
假设这个库看起来有点像这样:
#import <Foundation/Foundation.h>
@interface MyLibrary : NSObject
- (void)performRiskyTask;
@end
@implementation MyLibrary
- (void)performRiskyTask {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"Something went wrong"
userInfo:nil];
}
@end
现在,假设我尝试按以下方式使用此库:
do {
let myLibrary = MyLibrary()
try myLibrary.performRiskyTask()
} catch {
print("Error caught:", error)
}
Swift 编译器已经让我知道 try
表达式中没有抛出函数,并且 catch
块是不可访问的。事实上,当我 运行 代码时,它崩溃并出现 运行 时间错误:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Something went wrong'
*** First throw call stack:
(
0 CoreFoundation 0x00007ff8099861e3 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007ff8096e6c13 objc_exception_throw + 48
2 Exceptions 0x0000000100003253 -[MyLibrary performRiskyTask] + 83
3 Exceptions 0x00000001000035c2 main + 66
4 dyld 0x000000010001951e start + 462
)
libc++abi: terminating with uncaught exception of type NSException
正如 Swift 文档中所述,只有 NSError **
模式被 t运行 指定为 Swift 的 try-catch 模式。没有 built-in 方法来捕获 NSExceptions:
In Swift, you can recover from errors passed using Cocoa’s error pattern, as described above in Catch Errors. However, there’s no safe way to recover from Objective-C exceptions in Swift. To handle Objective-C exceptions, write Objective-C code that catches exceptions before they reach any Swift code. (https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift)
正如另一个答案中所指出的(参见
#import <Foundation/Foundation.h>
@interface ObjC : NSObject
+ (BOOL)catchException:(void (^)(void))tryBlock error:(NSError **)error;
@end
@implementation ObjC
+ (BOOL)catchException:(void (^)(void))tryBlock error:(NSError **)error {
@try {
tryBlock();
return YES;
} @catch (NSException *exception) {
if (error != NULL) {
*error = [NSError errorWithDomain:exception.name code:-1 userInfo:@{
NSUnderlyingErrorKey: exception,
NSLocalizedDescriptionKey: exception.reason,
@"CallStackSymbols": exception.callStackSymbols
}];
}
return NO;
}
}
@end
重写我的 Swift 代码(并在我的桥接 header 中导入 ObjC.h
)…
do {
let myLibrary = MyLibrary()
try ObjC.catchException {
myLibrary.performRiskyTask()
}
} catch {
print("Error caught:", error)
}
…,正在捕获异常:
Error caught: Error Domain=NSInternalInconsistencyException Code=-1 "Something went wrong" UserInfo={CallStackSymbols=(
0 CoreFoundation 0x00007ff8099861e3 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007ff8096e6c13 objc_exception_throw + 48
2 Exceptions 0x0000000100002c33 -[MyLibrary performRiskyTask] + 83
3 Exceptions 0x0000000100003350 $s10ExceptionsyycfU_ + 32
4 Exceptions 0x00000001000033d8 $sIeg_IeyB_TR + 40
5 Exceptions 0x0000000100002c9f +[ObjC catchException:error:] + 95
6 Exceptions 0x0000000100003069 main + 265
7 dyld 0x000000010001d51e start + 462
), NSLocalizedDescription=Something went wrong, NSUnderlyingError=Something went wrong}
Program ended with exit code: 0
所以这很好用。
但现在让我们说,我的库调用嵌套在其他 Swift 函数中,可能会抛出其他 Swift-native 错误:
func performTasks() throws {
try performOtherTask()
useLibrary()
}
func performOtherTask() throws {
throw MyError.someError(message: "Some other error has occurred.")
}
func useLibrary() {
let myLibrary = MyLibrary()
myLibrary.performRiskyTask()
}
enum MyError: Error {
case someError(message: String)
}
当然,我可以用 ObjC.catchException
块包围对我的库的每次调用,甚至可以围绕这个库构建一个 Swift 包装器。但相反,我想在一个地方捕获所有异常,而不必为每个方法调用编写额外的代码:
do {
try ObjC.catchException {
try performTasks()
}
} catch {
print("Error caught:", error)
}
但是,此代码无法编译,因为传递给 ObjC.catchException
方法的闭包不应抛出:
Invalid conversion from throwing function of type '() throws -> ()' to non-throwing function type '() -> Void'
要解决此问题,请将 void (^tryBlock)(void)
替换为 non-escaping void (^tryBlock)(NSError **)
并将 Objective-C 方法标记为“为 Swift 优化”(类似至
#import <Foundation/Foundation.h>
@interface ObjC : NSObject
+ (BOOL)catchException:(void (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error NS_REFINED_FOR_SWIFT;
@end
然后将错误指针传递给tryBlock
并根据是否发生错误更改return值:
@implementation ObjC
+ (BOOL)catchException:(void (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error {
@try {
tryBlock(error);
return error == NULL || *error == nil;
} @catch (NSException *exception) {
if (error != NULL) {
*error = [NSError errorWithDomain:exception.name code:-1 userInfo:@{
NSUnderlyingErrorKey: exception,
NSLocalizedDescriptionKey: exception.reason,
@"CallStackSymbols": exception.callStackSymbols
}];
}
return NO;
}
}
@end
然后使用以下扩展“优化”此方法:
extension ObjC {
static func catchException(_ block: () throws -> Void) throws {
try __catchException { (errorPointer: NSErrorPointer) in
do {
try block()
} catch {
errorPointer?.pointee = error as NSError
}
}
}
}
现在,Swift 代码可以编译:
do {
try ObjC.catchException {
try performTasks()
}
print("No errors.")
} catch {
print("Error caught:", error)
}
它捕获了 Swift 错误:
Error caught: someError(message: "Some other error has occurred.")
Program ended with exit code: 0
同样,它也会捕获NSException
:
func performOtherTask() throws {
//throw MyError.someError(message: "Some other error has occurred.")
}
Error caught: Error Domain=NSInternalInconsistencyException Code=-1 "Something went wrong" UserInfo={CallStackSymbols=(
0 CoreFoundation 0x00007ff8099861e3 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007ff8096e6c13 objc_exception_throw + 48
2 Exceptions 0x0000000100002643 -[MyLibrary performRiskyTask] + 83
3 Exceptions 0x00000001000030fa $s10Exceptions10useLibraryyyF + 58
4 Exceptions 0x0000000100002cfa $s10Exceptions12performTasksyyKF + 42
5 Exceptions 0x0000000100002c9f $s10ExceptionsyyKXEfU_ + 15
6 Exceptions 0x00000001000031b8 $sSo4ObjCC10ExceptionsE14catchExceptionyyyyKXEKFZySAySo7NSErrorCSgGSgXEfU_ + 56
7 Exceptions 0x000000010000339c $sSAySo7NSErrorCSgGSgIgy_AEIegy_TR + 12
8 Exceptions 0x000000010000340c $sSAySo7NSErrorCSgGSgIegy_AEIyBy_TR + 28
9 Exceptions 0x00000001000026b3 +[ObjC catchException:error:] + 99
10 Exceptions 0x0000000100002e93 $sSo4ObjCC10ExceptionsE14catchExceptionyyyyKXEKFZ + 371
11 Exceptions 0x00000001000029ce main + 46
12 dyld 0x000000010001d51e start + 462
), NSLocalizedDescription=Something went wrong, NSUnderlyingError=Something went wrong}
Program ended with exit code: 0
而且,当然,如果根本没有抛出异常,它将 运行 通过:
- (void)performRiskyTask {
// @throw [NSException exceptionWithName:NSInternalInconsistencyException
// reason:@"Something went wrong"
// userInfo:nil];
}
No errors.
Program ended with exit code: 0
编辑: 要支持抛出 NSExceptions 的 non-void 方法,请参阅 this post。