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.
source to share
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.
source to share
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.
source to share