我其實沒有問題。但是我遇到了一個關于使用 Swift 中NSException的單個 catch-block 處理 Swift 錯誤和 Objective-C 型別例外的問題,類似于:
do {
try object1.throwingObjectiveCMethod()
try object2.throwingSwiftMethod()
} catch {
print("Error:", error)
}
我找不到這個問題的答案,所以我想我會把它貼在這里,以防其他人遇到它。
退一步說,我需要在 Swift 中使用一些舊的 Objective-C 庫,這可能會拋出需要在 Swift 中捕獲的 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
As it is stated in the Swift docs, only the NSError ** pattern is translated to Swift's try-catch model. There is no built-in way to catch 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)
As pointed out in another answer (see here), this issue can be resolved using the following generic exception handler:
#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
Rewriting my Swift code (and importing ObjC.h in my bridging header) …
do {
let myLibrary = MyLibrary()
try ObjC.catchException {
myLibrary.performRiskyTask()
}
} catch {
print("Error caught:", error)
}
…, the exception is now being caught:
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
So this works great.
But now let's say, my library calls are nested inside other Swift functions which may throw other, Swift-native errors:
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)
}
Now of course, I could surround every call to my library by an ObjC.catchException block or even build a Swift wrapper around this library. But instead, I would like to catch all exceptions at a single spot without having to write extra code for every method call:
do {
try ObjC.catchException {
try performTasks()
}
} catch {
print("Error caught:", error)
}
This code doesn't compile, however, because the closure passed to the ObjC.catchException method shouldn't throw:
Invalid conversion from throwing function of type '() throws -> ()' to non-throwing function type '() -> Void'
uj5u.com熱心網友回復:
要解決此問題,請將 替換void (^tryBlock)(void)為非轉義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并根據是否發生錯誤更改回傳值:
@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
Similarly, it will also still catch the 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
And, of course, it will just run through if no exception is thrown at all:
- (void)performRiskyTask {
// @throw [NSException exceptionWithName:NSInternalInconsistencyException
// reason:@"Something went wrong"
// userInfo:nil];
}
No errors.
Program ended with exit code: 0
If this has been posted anywhere else and I have just completely wasted my time, feel free to let me know.
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/449987.html
標籤:swift objective-c exception
