NSExpression
Cocoa is the envy of other standard libraries when it comes to querying and arranging information. With NSPredicate
, NSSort
, and an occasional NSFetch
, even the most complex data tasks can be reduced into just a few, extremely-understandable lines of code.
Now, NSHipsters are no doubt already familiar with NSPredicate
(and if you aren’t, be sure to tune in next week!), but if we take a closer look at NSPredicate
, we see that NSPredicate
is actually made up of smaller, atomic parts: two NSExpression
s (a left-hand value & a right-hand value), compared with an operator (e.g. <
, IN
, LIKE
, etc.).
Because most developers only use NSPredicate
by means of +predicate
, NSExpression
is a relatively obscure class. Which is a shame, because NSExpression
is quite an incredible piece of functionality in its own right.
So allow me, dear readers, to express my respect and fascination with NSExpression
:
Evaluating Math
The first thing you should know about NSExpression
is that it lives to reduce terms. If you think about the process of evaluating an NSPredicate
, there are two terms and a comparator, so those two terms need to simplify into something that the operator can handle—very much like the process of compiling a line of code.
Which leads us to NSExpression
’s first trick: doing math.
let math Expression = NSExpression(format: "4 + 5 - 2**3")
let math Value = math Expression.expression Value With Object(nil, context: nil) as? Int
// 1
NSExpression *expression = [NSExpression expressionWithFormat:@"4 + 5 - 2**3"];
id value = [expression expressionValueWithObject:nil context:nil]; // => 1
It’s no Wolfram Alpha, but if your app does anything where evaluating mathematical expressions would be useful, well… there you go.
Functions
But we’ve only just scratched the surface with NSExpression
. Not impressed by a computer doing primary-school maths? How about high school statistics, then?
let numbers = [1, 2, 3, 4, 4, 5, 9, 11]
let stats Expression = NSExpression(for Function:"stddev:", arguments:[NSExpression(for Constant Value: numbers)])
let stats Value = stats Expression.expression Value With Object(nil, context: nil) as? Double
// 3.21859...
NSArray *numbers = @[@1, @2, @3, @4, @4, @5, @9, @11];
NSExpression *expression = [NSExpression expressionForFunction:@"stddev:" arguments:@[[NSExpression expressionForConstantValue:numbers]]];
id value = [expression expressionValueWithObject:nil context:nil]; // => 3.21859...
NSExpression
functions take a given number of sub-expression arguments. For instance, in the above example, to get the standard deviation of the collection, the array of numbers had to be wrapped with+expression
. A minor inconvenience (which ultimately allowsFor Constant Value: NSExpression
to be incredibly flexible), but enough to trip up anyone trying things out for the first time.
If you found the Key-Value Coding Simple Collection Operators (@avg
, @sum
, et al.) lacking, perhaps NSExpression
’s built-in statistical, arithmetic, and bitwise functions will pique your interest.
A word of caution: according to this table in Apple’s documentation for
NSExpression
, there is apparently no overlap between the availability of functions between OS X & iOS. It would appear that recent versions of iOS do, indeed, support functions likestddev:
, but this is not reflected in headers or documentation. Any details in the form of a pull request would be greatly appreciated.
Statistics
average:
sum:
count:
min:
max:
median:
mode:
stddev:
Basic Arithmetic
These functions take two NSExpression
objects representing numbers.
add:to:
from:subtract:
multiply:by:
divide:by:
modulus:by:
abs:
Advanced Arithmetic
sqrt:
log:
ln:
raise:to
Power: exp:
Bounding Functions
-
ceiling:
- (the smallest integral value not less than the value in the array) -
trunc:
- (the integral value nearest to but no greater than the value in the array)
math.h
Functions
Functions Shadowing So mentioned, because ceiling
is easily confused with ceil(3)
. Whereas ceiling
acts on an array of numbers, while ceil(3)
takes a double
(and doesn’t have a corresponding built-in NSExpression
function). floor:
here acts the same as floor(3)
.
floor:
Random Functions
Two variations—one with and one without an argument. Taking no argument, random
returns an equivalent of rand(3)
, while random:
takes a random element from the NSExpression
of an array of numbers.
random
random:
Binary Arithmetic
bitwise
And:with: bitwise
Or:with: bitwise
Xor:with: leftshift:by:
rightshift:by:
ones
Complement:
Date Functions
now
String Functions
lowercase:
uppercase:
No-op
noindex:
Custom Functions
In addition to these built-in functions, it’s possible to invoke custom functions in an NSExpression
. This article by Dave DeLong describes the process.
First, define the corresponding method in a category:
extension NSNumber {
func factorial() -> NSNumber {
return tgamma(self.double Value + 1)
}
}
@interface NSNumber (Factorial)
- (NSNumber *)factorial;
@end
@implementation NSNumber (Factorial)
- (NSNumber *)factorial {
return @(tgamma([self doubleValue] + 1));
}
@end
Then, use the function thusly (the FUNCTION()
macro in +expression
is shorthand for the process of building out with -expression
, et al.):
let function Expression = NSExpression(format:"FUNCTION(4.2, 'factorial')")
let function Value = function Expression.expression Value With Object(nil, context: nil) as? Double
// 32.578...
NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(4.2, 'factorial')"];
id value = [expression expressionValueWithObject:nil context:nil]; // 32.578...
The advantage here, over calling -factorial
directly is the ability to invoke the function in an NSPredicate
query. For example, a location:within
method might be defined to easily query managed objects nearby a user’s current location.
As Dave mentions in his article, the use cases are rather marginal, but it’s certainly an interesting trick to have in your repertoire.
Next week, we’ll build on what we just learned about NSExpression
to further explore NSPredicate
, and everything it has hidden up its sleeves. Stay tuned!