NSExpression
Written by Mattt Thompson on
Cocoa is the envy of other standard libraries when it
comes to querying and arranging information. With
NSPredicate
, NSSortDescriptor
,
and an occasional NSFetchRequest
, even
the most complex data tasks can be reduced into just
a few, extremelyunderstandable 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 lefthand value & a righthand value),
compared with an operator (e.g. <
,
IN
, LIKE
, etc.).
Because most developers only use
NSPredicate
by means of
+predicateWithFormat:
,
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.
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 primaryschool maths? How about high
school statistics, then?
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 subexpression arguments. For instance, in the above example, to get the standard deviation of the collection, the array of numbers had to be wrapped with+expressionForConstantValue:
. A minor inconvenience (which ultimately allowsNSExpression
to be incredibly flexible), but enough to trip up anyone trying things out for the first time.
If you found the KeyValue
Coding Simple Collection Operators
(@avg
, @sum
, et al.)
lacking, perhaps NSExpression
's builtin
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 Mac 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:toPower:

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)
Functions Shadowing math.h
Functions
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
builtin 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

bitwiseAnd:with:

bitwiseOr:with:

bitwiseXor:with:

leftshift:by:

rightshift:by:

onesComplement:
Date Functions

now
String Functions

lowercase:

uppercase:
Noop

noindex:
Custom Functions
In addition to these builtin 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:
@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
+expressionWithFormat:
is shorthand for
the process of building out with
expressionForFunction:
, et al.):
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:withinRadius:
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!