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")
}
Community
  • 1
  • 1
Chéyo
  • 7,590
  • 6
  • 24
  • 38
  • Why not use regex matching to see your list of acceptable equations. You should consider `automation theory` to understand calculations. – Dunes Buggy Jul 12 '14 at 07:57
  • 3
    It seems to be impossible to catch exceptions currently in Swift, compare http://stackoverflow.com/questions/24023112/try-catch-exceptions-in-swift 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. – Martin R Jul 12 '14 at 08: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. – Chéyo Jul 12 '14 at 14:11
  • I assume that Apple uses Object-C for Spotlight and there catching exceptions should work. – patrickS Jan 18 '15 at 07:19
  • 2
    I found this bridging file that works https://github.com/kongtomorrow/TryCatchFinally-Swift – Chéyo Feb 14 '15 at 02:58
  • Is this there still no simplistic answer? This seems ridiculous they would design it this way. – Craig Aug 12 '16 at 04:51
  • 1
    Possible duplicate of [Catching NSException in Swift](https://stackoverflow.com/questions/32758811/catching-nsexception-in-swift) – kelin Apr 07 '19 at 14:23

3 Answers3

12

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
}
derpoliuk
  • 1,456
  • 2
  • 23
  • 36
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
jrc
  • 15,615
  • 8
  • 60
  • 56
1

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)")
}
He Yifei 何一非
  • 2,292
  • 3
  • 31
  • 66