Using NSPredicate to Filter Data
Filtering data is one of the essential tasks in computing. With all the data available today, we need to apply certain limits and constraints to actually make it usable. What use is it to be able to scroll down a list of literally thousands of list items when you really care about one or two of them? Filtering and searching information make up a significant part of our work day – each time you use Google, you’re applying a filter to the huge set of data we call the internet.
Even on your local computer, you use services like Spotlight or the search field in your mail application all the time. Apps that lack decent searching and filtering capabilities sometimes are really hard to use, so as developers we should spend some time on thinking how to allow users to filter the data they’re dealing with.
In this post, I am going to focus on ways to filter data. We won’t be looking at the UI side of things – this is something I’ll do in a subsequent blog post.
Old-skool filtering
If you look at an arbitrary code base, chances are you’ll sooner or later run into a piece of code similar to this one:
NSMutableArray *oldSkoolFiltered = [[NSMutableArray alloc] init]; for (Book *book in bookshelf) { if ([book.publisher isEqualToString:@"Apress"]) { [oldSkoolFiltered addObject:book]; } } |
It’s a straight-forward approach to filtering an array of items (in this case, we’re talking about books) using a rather simple if-statement. Nothing wrong with this, but despite the fact we’re using a fairly simple expression here, the code is rather verbose. We can easily imagine what will happen in case we need to use more complicated selection criteria or a combination of filtering criteria.
Simple filtering with NSPredicate
Thanks to Cocoa, we can simplify the code by using NSPredicate. NSPredicate is the object representation of an if-statement, or, more formally, a predicate.
Predicates are expressions that evaluate to a truth value, i.e. true or false. We can use them to perform validation and filtering. In Cocoa, we can use NSPredicate to evaluate single objects, filter arrays and perform queries against Core Data data sets.
Let’s have a look at how our example looks like when using NSPredicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"publisher == %@", @"Apress" ]; NSArray *filtered = [bookshelf filteredArrayUsingPredicate:predicate]; |
Much shorter and better readable!
Filtering with Regular Expressions
Regular Expressions can be used to solve almost any problem
so it’s good to know you can use them in NSPredicates as well. To use regular expressions in your NSPredicate, you need to use the MATCHES operator.
Let’s filter all books that are about iPad or iPhone programming:
predicate = [NSPredicate predicateWithFormat:@"title MATCHES '.*(iPhone|iPad).*'"]; filtered = [bookshelf filteredArrayUsingPredicate:predicate]; dumpBookshelf(@"Books that contain 'iPad' or 'iPhone' in their title", filtered);
You need to obey some rules when using regular expressions in NSPredicate: most importantly, you cannot use regular expression metacharacters inside a pattern set.
Filtering using set operations
Let’s for a moment assume you want to filter all books that have been published by your favorite publishers. Using the IN operator, this is rather simple: first, we need to set up a set containing the publishers we’re interested in. Then, we can create the predicate and finally perform the filtering operation:
NSArray *favoritePublishers = [NSArray arrayWithObjects:@"Apress", @"O'Reilly", nil]; predicate = [NSPredicate predicateWithFormat:@"publisher IN %@", favoritePublishers]; filtered = [bookshelf filteredArrayUsingPredicate:predicate]; dumpBookshelf(@"Books published by my favorite publishers", filtered);
Advanced filtering thanks to KVC goodness
NSPredicate relies on key-value coding to achieve its magic. On one hand this means your classes need to be KVC compliant in order to be queried using NSPredicate (at least the attributes you want to query). On the other hand, this allows us to perform some very interesting things with very little lines of code.
Let’s for example retrieve a list of books written by authors with the name “Mark”:
predicate = [NSPredicate predicateWithFormat:@"authors.lastName CONTAINS %@", @"Mark" ]; filtered = [bookshelf filteredArrayUsingPredicate:predicate]; |
In case we’d want to return the value of one of the aggregate functions, we don’t need to use NSPredicate itself, but instead use KVC directly. Let’s retrieve the average price of all books on our shelf:
NSNumber *average = [bookshelf valueForKeyPath:@"@avg.price"]; |
Conclusion
The Cocoa libraries provide some very powerful abstractions which can make your life and that of the people reading your code much easier. It pays off to know about them, so go ahead and browse the Cocoa documentation and hunt for those gems!
Apple’s NSPredicate programming guide provides an in-depth documentation for NSPredicate containing all the things I didn’t cover in this post.
NSPredicate also plays an important role when querying data in Core Data, something we will need to have a look at in one of the next blog posts. Stay tuned!
Code
The code for this post is available on my github page: get forking!
Thanks for reading this post. Follow me on twitter here to be notified about updates and other posts I write. Or, subscribe to my RSS feed here. If you want to get in touch with me, use the contact form.






KVC is def the best method for this application.
Thanks
Very Good Information
قرأتها يا معلم
YNM
thank for your nice post
Great article! keep ahead
[...] 基本上,一个Predicate是一个表达式,返回Boolean值(true或false)。你可以以NSPredicate格式指定查询条件,然后使用NSPredicate 对象过滤数组中的数据。NSArray 提供了filteredArrayUsingPredicate: 方法,该方法返回一个新的数组,数组包含了匹配指定Predicate的对象。Predicate中的SELF关键字 – SELF contains[cd] %@ 指向比较对象(如菜单名称)。操作符[cd] 表示比较操作 – case 和 diacritic 不敏感。 如果你箱了解更多的Predicate知识,可以访问Apple的官方文档或者Peter Friese 编写的学习教程。 [...]
[...] Using NSPredicate to Filter Data – Peter Friese. Posted in Apple, ENG, iOS/SDK [...]
nice post
very useful, thanks for posting … sending best regards