Working together with NSRegularExpression and NSPredicate (when you should use NSException) (iOS/Xcode)


I've just started reading around search implementation in iOS (see Further Reading in this post) and it strikes me that if we want to implement Regex searching with NSPredicate in iOS then we immediately hit an issue, which is the lack of error handling in the NSPredicate methods.

Hacking it!

Now we could get around this by borrowing a newer class that has error handling built in, i.e. NSRegularExpression, and then if no errors are generated by an NSRegularExpression, we could presume ourselves safe to proceed.

This approach offers us the benefits of NSPredicate without the risks of crashing on a malformed string.

Here's some code to illustrate:

NSArray *books = @[@{@"title":@"An Amazing book"},@{@"title":@"A Semi-amazing Book"},@{@"title":@"An OK book"}];
 
    // Pointer to error
    NSError *error = NULL;
 
    NSString *keyTitle = @"title";

    // The string we are supposing the user entered
    NSString *inputString = @".*(Ama[a-z]{1,2}ng).*";

    // The string adapted so that it is understood in this context
    NSString *searchString = [NSString stringWithFormat:@"\\b%@\\b",inputString];

    // Tests to see if there is an error in the query
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:searchString options:NSRegularExpressionCaseInsensitive error:&error];
 
    if (!error) {
    // Now we know the expression is OK, we can use it in the predicate
   NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K MATCHES [cd] %@", keyTitle, [regex pattern]];
 
 
    NSArray *filtered = [books filteredArrayUsingPredicate:predicate];
     
    NSLog(@"Books that are better than OK: %@ \nFiltered using following search: %@", filtered, [regex pattern]);
    }
 
    else NSLog(@"%@",error);

Mess up the inputString as much as you like and see what happens! All should be OK.

Fixing it!

However, while this approach works in the majority of cases, it would not be favoured by most and the more traditional way would be to build a try and catch way of handling the exception.

For example:

@try {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K MATCHES [cd] %@", keyTitle, inputString];
     
     
        NSArray *filtered = [books filteredArrayUsingPredicate:predicate];
     
        NSLog(@"Books that are better than OK: %@ \nFiltered using following search: %@", filtered, inputString);
    }
    @catch (NSException *exception) {
        NSLog(@"%@",[exception reason]);
        failed = YES;
    }
    @finally {
        NSLog(@"Did fail? %hhd (0 = NO, 1= YES)",failed);
    }

A more sensible approach, even though the NSRegularExpression route is tempting for people unused to writing try and catch statements.

Further Reading


NSRegularExpression Tutorial and Cheat Sheet (Ray Wenderlich)

How To Add Search Bar in Table View (AppCoda)

Introduction to Predicates Programming Guide (Apple, Developer)

Using NSPredicate to Filter Data (Peter Friese)

Handling Exceptions (Apple, Developer)


Endorse on Coderwall

Comments