## Using custom functions with NSExpression

There are a bunch of built-in functions in the `NSExpression`

class that allow you to do some pretty neat stuff. They are: `average:`

, `sum:`

, `count:`

, `min:`

, `max:`

, `median:`

, `mode:`

, `stddev:`

, `add:to:`

, `from:subtract:`

, `multiply:by:`

, `divide:by:`

, `modulus:by:`

, `sqrt:`

, `log:`

, `ln:`

, `raise:toPower:`

, `exp:`

, `ceiling:`

, `abs:`

, `trunc:`

, `random`

, `random:`

, `now`

, `floor:`

, `uppercase:`

, `lowercase:`

, `bitwiseAnd:with:`

, `bitwiseOr:with:`

, `bitwiseXor:with:`

, `leftshift:by:`

, `rightshift:by:`

, `onesComplement:`

, and `noindex:`

. That’s a lot of functions! You have your arithmetic functions, some multi-parameter functions, some bitwise functions, and a few miscellaneous ones thrown in for good measure.

But… what if you want to find the factorial of a number? Or use any sort of trigonometric function? Or say you wanted to do something much more complex? Are you out of luck?

Fortunately, no. There *is* a way to do custom functions in `NSExpression`

, with the downside that the syntax is a little bit more complex than your standard functions.

The built-in functions can be written as if they were actual functions: `now()`

, `sqrt(42)`

, `6 * 9`

, etc. Unfortunately, we cannot do that with our custom functions. Oh well. Let’s get over that and move on.

### The Syntax

The syntax for all functions is:

```
FUNCTION(operand, 'function', arguments, ...)
```

In a nutshell, the result of the function is the result of invoking the `function`

method on `operand`

, while passing in the `arguments`

as parameters to the method.

If we dive into one of the `NSExpressions`

produced by a standard function, we learn some interesting things. Let’s example the function “`6*9`

”.

```
NSExpression * e = [(NSComparisonPredicate *)[NSPredicate predicateWithFormat:@"6*9 = 1"] leftExpression];
```

The `-operand`

of this expression is another `NSExpression`

. In the case of these built-in functions, it turns out that it’s a constant value expression encapsulating a `Class`

object of type `_NSPredicateUtilities`

. Some runtime introspection on this object shows that it has a whole bunch of class methods that correspond to all of the built-in functions, along with one that’s *not* listed in the documentation: `castObject:toType:`

. We previously discussed using this function with advanced date predicates.

The `operand`

is an `NSExpression`

(as opposed to an `id`

), because the object receiving the method could be the result of another expression.

The `function`

in our multiplication expression is “`multiply:by:`

”, as we would expect.

The `arguments`

to our multiplication is an `NSArray`

of `NSExpression`

objects. They are expressions for the same reason that the `operand`

is an `NSExpression`

. In our example, we have two expressions, both representing `NSNumbers`

as constant values.

### Custom Functions

Now that we understand the syntax behind `FUNCTION()`

, it should be fairly obvious how to go about making our own functions. Here’s what we need:

- An object on which to operate.
- A method to invoke
- Parameters to pass in (optional)

So let’s say we want to add a factorial function. We have two options on how to approach this:

- Make our factorial method receive the number as a parameter
- Make our factorial method operate on a number directly

I personally prefer the latter approach, since the syntax is slightly less complicated. Here’s what we would do:

```
@interface NSNumber (FactorialExpression)
- (NSNumber *) factorial;
@end
@implementation NSNumber (FactorialExpression)
- (NSNumber *) factorial {
double baseValue = [self doubleValue];
double result = tgamma(baseValue+1);
return [NSNumber numberWithDouble:result];
}
@end
```

(This method uses the Gamma Function, which allows for computing the factorial of non-integral numbers)

Then in our format string, we would express it like this:

```
FUNCTION(4.2, 'factorial')
```

If we evaluate that expression, we get `32.57809605033135`

, as we were hoping! The key thing here is realizing that `4.2`

will be boxed in an `NSNumber`

, which means that the `factorial`

method will be invoked on `NSNumber`

.

### Venturing Toward the Absurd

OK, we’ve got basic functions down. What about functions that take arguments? For this next example, we’ll take a look at a custom function being used in StackKit, a framework for accessing the Stack Overflow API.

At the heart of StackKit is a URL generation system that will turn a combination of an `NSPredicate`

, an `NSSortDescriptor`

, and a `Class`

into an HTTP GET request that can be used to retrieve data from one of the Stack Exchange sites (like http://stackoverflow.com). Due to the limited nature of the API, only certain predicates and sort descriptors are allowed, and only in very specific combinations.

One of the parts of “validating” an `NSPredicate`

is to verify that it is only using allowed keyPaths, and that these keyPaths are being compared with specific operators. Each of the “RequestBuilder” classes vends an `NSDictionary`

called the `recognizedPredicateKeyPaths`

. This dictionary is a map of keyPaths (as the key) to an `NSArray`

of `NSNumber`

objects. The `NSNumbers`

box an `NSPredicateOperatorType`

.

Since each builder recognizes different keyPaths and different operators, I had to get a little creative. Here’s what the code looks like:

```
p = [NSPredicate predicateWithFormat:@"FUNCTION(%@, 'sk_matchesRecognizedKeyPathsAndOperators:', SELF.recognizedPredicateKeyPaths) == YES", predicateToValidate];
[builders filterUsingPredicate:p];
```

In this case, `predicateToValidate`

is the user-supplied `NSPredicate`

. I’m going to be passing this predicate in as the operand to my FUNCTION. The method that will be invoked on it is `@selector(sk_matchesRecognizedKeyPathsAndOperators:)`

, which is conveniently provided via an `NSPredicate`

category. Finally, the argument to this function is the dictionary of the current request builder. If the `sk_matchesRecognizedKeyPathsAndOperators:`

method returns `YES`

, then we’ve passed this particular step of the validation.

The code for the `NSPredicate`

method is fairly simple; it simply iterates through all of the keys in the `NSDictionary`

, then extracts the subpredicates of the “`predicateToValidate`

" for each key. Of these subpredicates, if there’s one that’s using an operator that’s not recognized, then we abort and return `NO`

. Otherwise we’ll get to the end, find that all of our subpredicates are using allowed operators, and return `YES`

.

The result of those two lines of code is that the `builders`

array will be filtered to only contain the builders that allow the left keyPaths and operators that are specified in the user-provided predicate.

(For more fun, exciting, and absurd uses of `NSPredicate`

, check out `SKRequestBuilder.m`

.)

### A Caution

One of the things to be wary of when using custom functions is that * all* of the parameters to the method must be objects, and the return value of the method must also be an object! This means that you cannot do:

```
[NSPredicate predicateWithFormat:@"FUNCTION('a', 'isEqual:', 'a') == YES"];
```

If you try to evaluate that predicate, it will crash. `NSExpression`

is attempting to cast the result of the `isEqual:`

method as an object. However, `(id)1`

is not an object recognizable by Objective-C, no matter how you cast it.

Instead, if you needed to do this, you’d add a method that simply boxed the result in the appropriate `NSValue`

object and use that method:

```
@implementation NSString (CustomFunction)
- (NSNumber *) dd_isEqual:(id)other {
return [NSNumber numberWithBool:[self isEqual:other]];
}
@end
...
FUNCTION('a', 'dd_isEqual:', 'a')
```

This will work just fine.

### Wrap Up

Custom functions in `NSExpression`

are pretty neat. You don’t find yourself using them very often, but when you need them, they can be extremely handy!

- zuidwest likes this
- wickedfireisgreat reblogged this from funwithobjc
- buzz likes this
- phoenix-hurricane likes this
- digdog likes this
- funwithobjc posted this

**http://tmblr.co/Zt522y2kBaL8**