11

I am trying this code that is a calculator. How can I handle input from the user that is not valid?

//ANSWER: Bridging header to Objective-C// https://github.com/kongtomorrow/TryCatchFinally-Swift

Here is the same question but in objc but I want to do this in swift. Catching NSInvalidArgumentException from NSExpression

All I want to show is a message if it doesn't work, but now I am getting an exception when the user doesn't input the correct format.

import Foundation

var equation:NSString = "60****2"  // This gives a NSInvalidArgumentException', 
let expr = NSExpression(format: equation) // reason: 'Unable to parse the format string
if let result = expr.expressionValueWithObject(nil, context: nil) as? NSNumber {
    let x = result.doubleValue
    println(x)
} else {
    println("failed")
}
7
  • Why not use regex matching to see your list of acceptable equations. You should consider automation theory to understand calculations. Commented Jul 12, 2014 at 7:57
  • 3
    It seems to be impossible to catch exceptions currently in Swift, compare stackoverflow.com/questions/24023112/… and the linked questions. - Unfortunately, NSExpression (and other Foundation classes) does not follow Apple's advice to use an error parameter instead of throwing exceptions. Commented Jul 12, 2014 at 8:25
  • This is what apple uses for the the Mac spotlight inline calculations. I wonder if I could access the built in parser to check the string before passing it to the expression. Commented Jul 12, 2014 at 14:11
  • 2
    I found this bridging file that works github.com/kongtomorrow/TryCatchFinally-Swift Commented Feb 14, 2015 at 2:58
  • 1
    Possible duplicate of Catching NSException in Swift Commented Apr 7, 2019 at 14:23

3 Answers 3

13

More "Swifty" solution:

@implementation TryCatch

+ (BOOL)tryBlock:(void(^)())tryBlock
           error:(NSError **)error
{
    @try {
        tryBlock ? tryBlock() : nil;
    }
    @catch (NSException *exception) {
        if (error) {
            *error = [NSError errorWithDomain:@"com.something"
                                         code:42
                                     userInfo:@{NSLocalizedDescriptionKey: exception.name}];
        }
        return NO;
    }
    return YES;
}

@end

This will generate Swift code:

class func tryBlock((() -> Void)!) throws

And you can use it with try:

do {
    try TryCatch.tryBlock {
        let expr = NSExpression(format: "60****2")
        ...
    }
} catch {
    // Handle error here
}
Sign up to request clarification or add additional context in comments.

4 Comments

The suggested workarounds don't work for a framework built in Swift. Still hoping for a real solution.
@WimHaanstra we have this in our Swift framework: github.com/eggheadgames/SwiftTryCatch
Thanks, got it working now. Made the header public and now it works (?)
@WimHaanstra you're welcome! Do you mean TryCatch header, or SwiftTryCatch?
8

This is still an issue in Swift 2. As noted, the best solution is to use a bridging header and catch the NSException in Objective C.

https://medium.com/swift-programming/adding-try-catch-to-swift-71ab27bcb5b8 describes a good solution, but the exact code doesn't compile in Swift 2 because try and catch are now reserved keywords. You'll need to change the method signature to workaround this. Here's an example:

// https://medium.com/swift-programming/adding-try-catch-to-swift-71ab27bcb5b8

@interface TryCatch : NSObject

+ (void)tryBlock:(void (^)())try catchBlock:(void (^)(NSException *))catch finallyBlock:(void (^)())finally;

@end

@implementation TryCatch

+ (void)tryBlock:(void (^)())try catchBlock:(void (^)(NSException *))catch finallyBlock:(void (^)())finally {
    @try {
        try ? try() : nil;
    }
    @catch (NSException *e) {
        catch ? catch(e) : nil;
    }
    @finally {
        finally ? finally() : nil;
    }
}

@end

Comments

2

A nice solution editing from https://github.com/kongtomorrow/TryCatchFinally-Swift:

First create TryCatch.h & TryCatch.m and bridge them to Swift:

TryCatch.h

#import <Foundation/Foundation.h>

void tryCatch(void(^tryBlock)(), void(^catchBlock)(NSException *e), void(^finallyBlock)());

TryCatch.m

#import <Foundation/Foundation.h>

void tryCatch(void(^tryBlock)(), void(^catchBlock)(NSException *e), void(^finallyBlock)()) {
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        catchBlock(exception);
    }
    @finally {
        finallyBlock();
    }
}

Then create the class TryCatch in Swift:

func `try`(`try`:()->()) -> TryCatch {
    return TryCatch(`try`)
}
class TryCatch {
    let tryFunc : ()->()
    var catchFunc = { (e:NSException!)->() in return }
    var finallyFunc : ()->() = {}

    init(_ `try`:()->()) {
        tryFunc = `try`
    }

    func `catch`(`catch`:(NSException)->()) -> TryCatch {
        // objc bridging needs NSException!, not NSException as we'd like to expose to clients.
        catchFunc = { (e:NSException!) in `catch`(e) }
        return self
    }

    func finally(finally:()->()) {
        finallyFunc = finally
    }

    deinit {
        tryCatch(tryFunc, catchFunc, finallyFunc)
    }
}

Finally, use it! :)

`try` {
    let expn = NSExpression(format: "60****2")

    //let resultFloat = expn.expressionValueWithObject(nil, context: nil).floatValue
    // Other things...
    }.`catch` { e in
        // Handle error here...
        print("Error: \(e)")
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.