I want to validate user created expressions (like "2+2", "5+7" or more complex). I use NSExpression class to parse and calculate this 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 valid expression ("2+2") - I get the result. But sometime user can provide wrong string ("2+" for example). With this string my app 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 code above don't do this. Now I use Objective C class (with same logic), calling this method from my swift code, and in that class I really can catch such exception:
+(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 correct parse state (nil means problems with expression) and result (NSNumber), but I really want to understand how to do all this things correct and entirely in Swift.
NSInvalidArgumentException
is not a catchable error in the sense of Java exceptions. Apple doesn't guarantee that your program will be in a correct state when you catch this exception and chances are that something will go wrong.You should probably use some other mechanism to check if the string is valid before you pass it to the method.
This is what the book Using Swift with Cocoa and Objective-C has to say:
[My bold]
Having just skimmed the reference for
NSExpression
I can't see a simple way around the issue. The quote above recommends writing a a bit of Objective-C code to do it. The simplest way is probably to create a C function:Declaration:
Definition
The function returns nil for expressions that are in error.
You could probably add an
NSError**
parameter to be used when there is a failure. You could also probably make this a method in a category onNSExpression
and then the return nil for error/fill in NSError pattern would probably be imported to Swift as a Swift throwing method.I should say, by the way, that an Objective-C exception is not really guaranteed to leave your program in a consistent state. Fingers crossed it is OK in this case.