Correct way to catch NSInvalidArgumentException when using NSExpression

I want to test user-expressed expressions (eg "2 + 2", "5 + 7" or more complex). I am using the NSExpression class to parse and evaluate these expressions. This is my playground code:

import UIKit

let string = "2+2"

var ex:NSExpression?
do {
    ex = NSExpression(format: string)
}
catch {
    print("String is not valid expression")
}

if let result = ex?.expressionValue(with: nil, context: nil) as! NSNumber? {
    print("result is \(result)")
}

      

When I use a valid expression ("2 + 2"), I get the result. But sometimes the user may provide an invalid string (eg "2+"). With this line, my application crashes with this:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "2+ == 1"'

      

I don't understand how I can catch this exception and why the code above doesn't do it. Now I am using the Objective C class (with the same logic) by calling this method from my swift code and in this class I can actually catch an exception like this:

+(NSNumber *)solveExpression:(NSString *)string
{
    id value;
    @try {
        NSExpression *ex = [NSExpression expressionWithFormat:string];
        value = [ex expressionValueWithObject:nil context:nil];
    }
    @catch (NSException *e) { }
    return value;
}

      

This works and I can get the correct parsing state (nil means expression problems) and the result (NSNumber), but I really want to understand how to do it all correctly and completely in Swift.

+3


source to share


2 answers


This is what the book using Swift with Cocoa and Objective-C has to say :

While Swift's error handling is similar to Objective-C's exception handling, it is a completely separate functionality. If an Objective-C method throws an exception at runtime, Swift triggers a runtime error. There is no way to recover from Objective-C exceptions directly in Swift. Any exception handling behavior must be implemented in the Objective-C code used by Swift.

[My bold]

Just by removing the link for NSExpression

, I don't see an easy way to solve the problem. The quote above recommends writing the Objective-C code to do this. The simplest way is probably to create a C function:

Declaration:

extern NSExpression* _Nullable makeExpression(NSString* format _Nonnull);

      



Definition

NSExpression* _Nullable makeExpression(NSString* format _Nonnull)
{
    NSExpression* ret = nil;
    @try 
    {
        // create and assign the expression to ret
    }
    @catch(NSException* e)
    {
        // ignore
    }
    return ret;
}

      

The function returns nil for expressions in error.

Perhaps you can add a parameter NSError**

to be used on failure. You could probably also make this method in a category on NSExpression

, and then the return nil for error / padding in the NSError pattern would probably be imported into Swift as a Swift throwing method.

I have to say, by the way, that Objective-C exception doesn't really guarantee your program will be in a consistent state. The fingers crossed, in this case everything is in order.

+3


source


NSInvalidArgumentException

not a catchy bug in the sense of Java exceptions. Apple does not guarantee that your program will be in the correct state when you catch this exception, and there is a chance that something will go wrong.



You should probably use some other mechanism to validate the string before passing it to the method.

+2


source







All Articles