Correct way to catch NSInvalidArgumentException wh

2020-03-25 04:30发布

问题:

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.

回答1:

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

Although Swift error handling resembles exception handling in Objective-C, it is entirely separate functionality. If an Objective-C method throws an exception during 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 Objective-C code used by Swift.

[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:

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 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 on NSExpression 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.



回答2:

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.