Catching Nsexception in Swift

Catching NSException in Swift

Here is some code, that converts NSExceptions to Swift 2 errors.

Now you can use

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

Don't forget to add this to your "*-Bridging-Header.h":

#import "ObjC.h"

Catching Both Objective-C and Swift Exceptions

To resolve the issue, replace the void (^tryBlock)(void) by a non-escaping void (^tryBlock)(NSError **) and mark the Objective-C method as "refined for Swift" (similar to this answer):

#import <Foundation/Foundation.h>

@interface ObjC : NSObject

+ (BOOL)catchException:(void (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error NS_REFINED_FOR_SWIFT;

@end

Then pass the error pointer to the tryBlock and change the return value depending on whether an error occurred:

@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

Then "refine" this method using the following extension:

extension ObjC {
static func catchException(_ block: () throws -> Void) throws {
try __catchException { (errorPointer: NSErrorPointer) in
do {
try block()
} catch {
errorPointer?.pointee = error as NSError
}
}
}
}

Now, the Swift code does compile:

do {
try ObjC.catchException {
try performTasks()
}
print("No errors.")
} catch {
print("Error caught:", error)
}

And it catches the Swift error:

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

EDIT: To support non-void methods throwing NSExceptions, see this post.

Catch Objective-C exception in Swift

see this answer:

//
// ExceptionCatcher.h
//

#import <Foundation/Foundation.h>

NS_INLINE NSException * _Nullable tryBlock(void(^_Nonnull tryBlock)(void)) {
@try {
tryBlock();
}
@catch (NSException *exception) {
return exception;
}
return nil;
}

Swift Catch Runtime Exceptions

To catch Obj-C exceptions in Swift, I am using a simple Obj-C class:

#import "ObjC2Swift.h"

@implementation ObjC

+ (id)catchException:(id(^)())tryBlock error:(__autoreleasing NSError **)error {
@try {
id result = tryBlock();
return result;
}
@catch (NSException *exception) {
if (error) {
*error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo];
}
return nil;
}
}

@end

In Swift called as

let result = try? ObjC.catchException { ... dangerous code here ... }

You might need a different variant for blocks that don't return anything.

Not to be abused. Obj-C exception are evil and I am using this only because I need a library that uses them.

Exception is not being caught

Swift converts Objective-C methods with nullable returns and trailing NSError** parameters to methods that throw in Swift. But, in Objective-C, you can also throw exceptions. These are distinct from NSErrors and Swift does not catch them. In fact there is no way to catch them in Swift. You would have to write an Objective-C wrapper that catches the exception and passes it back in some way Swift can handle.

You can find this in the Apple document Handling Cocoa Errors in Swift in the Handle Exceptions in Objective-C Only section.

So it turns out that you can catch it, but it is worthwhile considering whether you should (see comments from @Sulthan below). To the best of my knowledge most Apple frameworks are not exception-safe (see: Exceptions and the Cocoa Frameworks) so you can't just catch an exception and continue on as if nothing happened. Your best bet is save what you can and exit as soon as possible. Another issue to consider is that unless you rethrow the exception, frameworks such as Crashlytics won't report it back to you. So, if you did decide to catch it you should log it and/or rethrow it so that you know that it is happening.

Error Handle of NSRangeException in swift

At last I reached at following solution that in swift if need to handle range exceptions then we need to do this

var sourceString = "This is demo text"
let sourceData = sourceString.data(using: String.Encoding.utf8)! // sourceData is equivalent to "wav" from question

Total data length(sourceData.length) should be maximum from your
staring point(4000) and length of data(20) which you need to retrieve

guard sourceData.length >= (4000 + 20) else{

throw LevelParsingException.InvalidLevelContent
}


Related Topics



Leave a reply



Submit