NSPredicate is a nifty class. It’s intended use is to be an objected-oriented way of expressing a truth statement. We see this being used with things like
-[NSFetchRequest setPredicate:], and more.
NSPredicate, however, can offer us a lot more.
There are 2 things that make
NSPredicate so insanely awesome:
- Its string parser
- The evaluation power of
NSPredicate’s support classes)
One of the best things about
NSPredicate is its parsing engine. Given a well-formatted
NSPredicate will turn it into a tree of
NSComparisonPredicate objects. This can be used to our advantage. If we have a string that represent some boolean expression, we can tap
NSPredicate to parse it for us and give us back an organized syntax tree.
For this example, let’s consider the following:
(a | b) & c | (b | d)
If we just try to run this through the predicate parser, we’ll get an exception, for 2 reasons:
- A predicate is a truth statement. This expression is simply a value. It is not being compared to anything, and as such does not represent a true or false statement, but simply a value.
- Each sub-expression of a predicate must itself be a predicate. Since “
c” (for example) is not being compared to anything, it is not a valid statement.
Fixing the first problem is easy. We can just tack “
= 0” onto the end of the statement:
(a | b) & c | (b | d) = 0
As for fixing the second, we can do that with a little bit of find-and-replace by regular expression. For simplicity, we’ll say that each one of these identifiers (
d) must be alphanumeric. We can then use something like
NSRegularExpression (on iOS) or RegexKit (on Mac) to replace “
([a-z0-9]+)” with “
\1 = 0”, thereby giving us:
(a = 0 | b = 0) & c = 0 | (b = 0 | d = 0) = 0
We can now run this through
+[NSPredicate predicateWithFormat:] to get our parsed tree. From there you can recursively walk the tree and manipulate things as much as you like.
Let’s say you have a string that contains some sort of mathematical statement. Perhaps it’s user-entered, or perhaps it’s dynamically generated. For our purposes, we’re going to use:
22/7.0 + (13*42) - 1024
Our calculator shows us that we should be getting
-474.857142857142857 as the result.
But how can we evaluate this dynamically? Well, you could use something like Graham Cox’s excellent
GCMathParser to parse and evaluate it, but this has a limitation: it’s a bit difficult (read: nearly impossible) to extend to use functions not supported by the parser.
Fortunately, we can abuse
NSPredicate to do the work for us.
NSPredicate has a wonderful parsing engine (which we’ve already abused), and it builds a tree built out of
NSExpression objects, which have this wonderful method called
-expressionValueWithObject:context: which will evaluate everything for us. Unfortunately, there’s no way to build an
NSExpression tree out of an arbitrary
So we just need to make out statement into a predicate, run it through the parser, and extract the appropriate value! This is really simple. To make something into a predicate, we simply need to add an operator and a comparison value:
22/7.0 + (13*42) - 1024 = 0
Now, we can pump it through
NSPredicate * parsed = [NSPredicate predicateWithFormat:@"22/7.0 + (13*42) - 1024 = 0"];
We know that this is an
NSComparisonPredicate (since it’s of the form
<expression> <operator> <expression>), so we can simply extract the
NSExpression * left = [(NSComparisonPredicate *)parsed leftExpression];
And finally, we can now evaluate it:
NSNumber * result = [left expressionValueWithObject:nil context:nil]; NSLog(@"result: %@", result);
Wonderfully, this logs
-474.8571428571429, which is exactly what we were hoping for.