Catch NSException in Swift

The following code in Swift raises an NSInvalidArgumentException:

task = NSTask() task.launchPath = "/SomeWrongPath" task.launch() 

How can I catch the exception? As far as I understand, try / catch in Swift are errors that occur in Swift, and not for NSExceptions created from objects like NSTask (which, I think, is written in ObjC). I'm new to Swift, maybe I'm missing something obvious ...

Edit : here is the error radar (specifically for NSTask): openradar.appspot.com/22837476

+62
exception cocoa-touch swift swift2 foundation nsexception
Sep 24 '15 at 10:16
source share
5 answers

Here is some code that converts NSEx exceptions 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 

Remember to add this to your "* -Bridging-Header.h":

 #import "ObjC.h" 
+114
Apr 6 '16 at 2:54 on
source share

I suggest making a C function that will catch the exception and return an NSError. Then use this function.

A function might look like this:

 NSError *tryCatch(void(^tryBlock)(), NSError *(^convertNSException)(NSException *)) { NSError *error = nil; @try { tryBlock(); } @catch (NSException *exception) { error = convertNSException(exception); } @finally { return error; } } 

And with a little help on the bridge, you just need to call:

 if let error = tryCatch(task.launch, myConvertFunction) { print("An exception happened!", error.localizedDescription) // Do stuff } // Continue task 

Note. I really did not test it, I could not find a quick and easy way to have Objective-C and Swift on the playground.

+12
07 Oct '15 at 11:56
source share

TL; DR: use Carthage to enable https://github.com/eggheadgames/SwiftTryCatch or CocoaPods to enable https://github.com/ravero/SwiftTryCatch .

Then you can use this code without fear that it will crash your application:

 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's accepted answer works as intended, but as we found out, things get a little interesting if you try to include this Objective-C code in most Swift structures and then run the tests. We had problems with the lack of a class function (Error: using an unresolved identifier). Therefore, for this reason, and just general ease of use, we packaged it as a Carthage library for general use.

Oddly enough, we could use the Swift + ObjC environment in other places without problems, these were only unit tests for the framework that was struggling.

PR requested! (It would be nice to have his CocoaPod and Carthage build combos, as well as have some tests).

+9
Dec 28 '15 at
source share

As noted in the comments, this API throws exceptions for situations that might otherwise be repaired is an error. Edit it and ask for an NSError based alternative. Basically, the current state of affairs is an anachronism, since NSTask dates back before Apple standardized with exceptions only for programmer errors.

In the meantime, although you can use one of the mechanisms from the other answers to throw exceptions in ObjC and pass them to Swift, keep in mind that doing this is not very safe. The stack expansion mechanism, with the exception of ObjC (and C ++), is fragile and fundamentally incompatible with ARC. This is part of why Apple uses exceptions only for programmer errors - the idea is that you can (theoretically, at least) parse all cases of exceptions in your application during development and not have any exceptions that occur in your production code. (Fast errors or NSError s, on the other hand, can indicate recoverable situational or user errors.)

A safer solution is to provide probable conditions that can cause the API to throw exceptions and handle them before calling the API. If you are indexing in an NSArray , first check its count . If you install launchPath on NSTask to something that may not exist or may not execute, use NSFileManager to verify that before running the task.

+3
Dec 28 '15 at 23:18
source share

As stated in the documentation , this is a simple and easy way to do this:

 do { try fileManager.moveItem(at: fromURL, to: toURL) } catch let error as NSError { print("Error: \(error.domain)") } 
0
Jun 05 '19 at 15:39
source share



All Articles