I think my first experience of using the NSPredicate
class on the iPhone was with core data queries but it is has many other useful applications. I had the need the other day to process a list of filenames and match against a simple regular expression. The code to create the array of filenames that I needed to process looked something like this:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *defaultPath = [[NSBundle mainBundle] resourcePath];
NSError *error;
NSArray *directoryContents = [fileManager contentsOfDirectoryAtPath:defaultPath
error:&error];
The first few lines just setup a search of the resource directory in the main application bundle so that the array directoryContents
ends up containing the filenames of all the files in the directory. I then need to process that array and pull out the filenames that match my pre-defined regular expression.
NSPredicate Format Strings
The NSPredicate
class provides a general way to specify a query that can then be applied to filter an array. The actual query to use is specified when creating the predicate as an argument to the predicateWithFormat:
method. Some examples will hopefully make it clear:
Simple match
The simplest example is when you just need an exact match against a single value. This is a fairly meaningless example in this case but illustrates the basic technique:
NSString *match = @"imagexyz-999.png";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF == %@", match];
NSArray *results = [directoryContents filteredArrayUsingPredicate:predicate];
The query string is simply SELF == %@
where SELF
refers to the each element in the array. To use the predicate we apply it to the array of file names using filterArrayUsingPredicate:
. In this trivial (and pointless) example the results array would contain a single string imagexyz-999.png
assuming that file existed in the directory.
Wildcard match
To match using a wildcard string the query can use like
instead of the comparison operator and include the *
(match zero or more characters) or ?
(match exactly 1 character) as wildcards as follows:
NSString *match = @"imagexyz*.png";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF like %@", match];
NSArray *results = [directoryContents filteredArrayUsingPredicate:predicate];
In this case the results array would contain the filenames that match imagexyz*.png
(e.g. imagexyz-001.png
, imagexyz-002.png
, etc.).
Case Insensitive match
To make the previous example use a case insensitive match the like operator is modified to like[c]
. There is also an additional option to match in a diacritic-insenstive way to ignore accented characters by using like[d]
. You can combine these two options as follows:
NSString *match = @"imagexyz*.png";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF like[cd] %@", match];
NSArray *results = [directoryContents filteredArrayUsingPredicate:predicate];
Other string comparisons
In addition to like you can also use beginswith
, endswith
, contains
and matches
. These are mostly self-explanatory with the exception perhaps of matches
. With matches
you can specify a full regular expression which allows more complex matches than is possible with just the *
and ?
wildcard characters. For example to match filenames of the form imagexyz-ddd.png
where ddd
are all digits:
NSString *match = @"imagexyz-\\d{3}\\.png";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF matches %@", match];
NSArray *results = [directoryContents filteredArrayUsingPredicate:predicate];
I will not try to explain regular expression syntax here but note that the backslash (\
) characters are escaped by using a double-backslash. Also note that strict ICU regular expression formats are used.