Evaluating Expressions in iOS with Objective-C and Swift

Recently, I have been researching ways to evaluate string expressions such as "(1 + 3) * 5 / 2" in iOS. I have found that there is fantastic support to do this using the built in NSExpression and NSPredicate classes. In this post I will detail how these two classes can be utilized to evaluate expressions. The examples provided will be in both Objective-C and Swift.

Expression Types

The concept of an expression can mean many different things. For this post I will mainly focus on what I call numeric expressions and logical expressions. Numeric expressions are expressions that produce a numeric result when evaluated, such as "1 + 5 - 1" and "4 / 5". Logical expressions are expressions that produce a logical true or false result when evaluated, such as "4 > 2 + 1" and "3 > 5 or 2 + 1 == 3".

Notice that these expression strings only include numbers, mathematical operators, and logical operators. If the expressions you are working with are not in this format, you will need to convert them to this format before evaluating them.

Numeric Expressions

Let’s begin by discussing how you can evaluate numeric expressions in iOS using the NSExpression class. The code required to do this (without error handling) is incredibly simple.


NSString *numericExpression = @"4 + 3 - 2";
NSExpression *expression = [NSExpression expressionWithFormat:numericExpression];
NSNumber *result = [expression expressionValueWithObject:nil context:nil];


var numericExpression = "4 + 3 - 2"
let expression = NSExpression(format: numericExpression)
var result = expression.expressionValueWithObject(nil, context: nil) as NSNumber

The above code takes in the expression "4 + 3 - 2" and uses NSExpression to produce a result of 5.

Logical Expressions

Logical expression are a little be more complicated, though not by much. To evaluate them, NSPredicate can be used (which uses NSExpression behind the scenes). A logical expression such as "1 + 2 > 2 || 4 - 2 = 0" can be evaluated as follows.


NSString *logicalExpression = @"1 + 2 > 2 || 4 - 2 = 0";
NSPredicate *predicate = [NSPredicate predicateWithFormat:logicalExpression];
BOOL result = [predicate evaluateWithObject:nil];


var logicalExpression = "1 + 2 > 2 || 4 - 2 = 0"
let predicate = NSPredicate(format: logicalExpression)
var result = predicate!.evaluateWithObject("") as Bool

Unlike the NSExpression example where the numeric result was stored in a NSNumber, the result from calling NSPredicate.evaluateWithObject is a BOOL.

Expression Functions

The above approaches allow for a enormous variety of expressions to be evaluated and require very little code to do so.

If that isn’t impressive enough, both NSExpression and NSPredicate also support a set of built-in functions that can be used in the expression string. This set includes functions such as sqrt, log, ln, exp, ceiling, abs, trunc, floor, and several others. More details can be found on the NSExpression docs page. These functions can be used directly in the expression string. For example:


NSString *stringExpression = @"sqrt(4 + 3 - 2) + log(5)";
NSExpression *expression = [NSExpression expressionWithFormat:stringExpression];
NSNumber *result = [expression expressionValueWithObject:nil context:nil];


var numericExpression = "sqrt(4 + 2 - 2)"
let expression = NSExpression(format: numericExpression)
var result = expression.expressionValueWithObject(nil, context: nil) as NSNumber

The above expression computes the square root of 5 and adds it to the log of 5.

Custom Functions

If the function you need isn’t in the supported list, you can even write your own custom functions. Suppose we need a function that squares its input and subtracts 5 (an obviously contrived example). In Objective-C we add write this function by adding a category method to NSNumber. Let’s call our category NSNumber+Functions.


#import "NSNumber+Functions.h"
@implementation NSNumber (Functions)
- (NSNumber*)squareAndSubtractFive {
return @(([self doubleValue] * [self doubleValue]) - 5);
}
@end

If you are working in Swift, you will need to write an extension method on NSNumber.


public extension NSNumber {
func squareAndSubtractFive() -> NSNumber {
return self.doubleValue * self.doubleValue - 5
}
}

Calling the Objective-C and Swift custom functions then looks like this.


NSString *stringExpression = @"function(5, 'squareAndSubtractFive')";
NSExpression *expression = [NSExpression expressionWithFormat:stringExpression];
NSNumber *result = [expression expressionValueWithObject:nil context:nil];


var stringExpression = "function(5, 'squareAndSubtractFive')"
let expression = NSExpression(format: numericExpression)
var result = expression.expressionValueWithObject(nil, context: nil) as NSNumber

After calling squareAndSubtractFive the variable result will hold the value 20.

While the function(input, 'functionName') syntax is a little ugly, it provides a lot of power. You can even write functions that take in additional parameters. As an example, suppose we needed a Gaussian function that takes in an input value, a Gaussian mean, and a Gaussian variance. To do this we would implement another NSNumber category method.


- (NSNumber*)gaussianWithMean:(NSNumber*)mean andVariance:(NSNumber*)variance {
double value = [self doubleValue];
double valueMinusMean = value - [mean doubleValue];
return @(exp(- (valueMinusMean * valueMinusMean) / [variance doubleValue]));
}

To call this function we would use the same syntax as above, though we would also pass in mean and variance parameters.


NSString *gaussianExpression = @"function(0.25, 'gaussianWithMean:andVariance:', 0, 0.5)" // mean = 0, variance = 0.5
NSExpression *expression = [NSExpression expressionWithFormat:gaussianExpression];
NSNumber *result = [expression expressionValueWithObject:nil context:nil];

Other Considerations

At this point, you probably realize that with a little creativity you can do almost everything with NSExpression and NSPredicate. The one caveat to point out is error handling. If you pass an invalid expression string NSExpression and NSPredicate will throw an NSInvalidArgumentException. You can, of course, work around this in Objective-C by wrapping your expression evaluation in a @try @catch block. However, it doesn’t look like there is a way to catch exceptions in Swift. The obvious workaround would be to wrap the Swift class with an Objective-C wrapper with a try-catch block, but that doesn’t feel like a great solution.

Conversation
  • Ivan Vassilev says:

    Hi Mat,

    I have a issue with the following matter:

    I have 2 fields in my record type in CloudKit (positive, negative)
    One for positive rates, the other for negative. Both are of type int 64.
    For every rate I increment the one or another field.

    Is it possible to fetch records that match the following criteria using NSPredicate?

    positive/positive+negative > 50/100
    i.e.: give me results with rating > 50%

  • Ayy says:

    In the last part you only explained the Gaussian function on Objective C, please post it on Swift too!

  • Fran says:

    Hi!

    I’m trying to work with this logical expression “YES && NO || NO” but it’s crashing. I used your logical expression example. Any suggestions?

    Thanks!

  • Marc says:

    Hi!

    is there any (simple ?) way to include trigonometric functions ?

    Thanks a lot.

  • Comments are closed.