Searching NSArray using NSPredicate

I often find myself searching an NSArray for an object with a property matching a particular (and usually unique within the array) value. Returning the first match found in the array is usually sufficient. All of the methods on NSArray one might use for this task return the index of the matching object which requires you to make another method call to retrieve the object from the array. I normally end up using indexOfObjectPassingTest: which has the added complexity of taking a block parameter, but lets you create any test you like to define what constitutes a match.

I wanted to a simpler way to retrieve an object in one line of code, preferably by using an NSPredicate to specify the object I wanted. There is already a method on NSArray that allows you to filter an array using a predicate, but it returns a new array. That feels a bit clunky and still requires another method call to retrieve the first item in the new array, but at least it allowed me to used a predicate to define my search.

Ultimately, I wrote a category on NSArray and added two methods which essentially wrap the two previously mentioned methods that already exist on NSArray. I decided to test my new methods and decide which would be the keeper. They each take an NSPredicate and return an object (the first matching the predicate).

The first method filters the array with the predicate and simply returns the first item in the filtered array. Note the temporary suffix “A” at the end of the method name to remind me this is the version which uses arrays.

-(id)findFirstMatchingPredicateA:(NSPredicate *)predicate {
    // create new array by filtering with predicate and returning first object
    NSArray *filteredArray = [self filteredArrayUsingPredicate:predicate];
    return [filteredArray objectAtIndex:0];
}

The second method I wrote calls indexOfObjectPassingTest: and uses the captured NSPredicate parameter in the block to find the index of the matching object which is then retrieved and returned. This method name has a temporary “B” suffix to remind me it uses a block.

-(id)findFirstMatchingPredicateB:(NSPredicate *)predicate { 
    // find object with block NSUInteger index = [self indexOfObjectPassingTest:^(id obj, NSUInteger idx, BOOL *stop) { 
    *stop = [predicate evaluateWithObject:obj] ? YES : NO; return *stop;
    }];
    return [self objectAtIndex:index];
}

Both methods meet my requirement of taking a predicate and returning an object in one simple line of code. They are called similarly. If you had an array of Person objects named “coolPeople”, retrieving the first person named “Steve” would look like the following.

Person *p = [coolPeople findFirstMatchingPredicateB:[NSPredicate predicateWithFormat:@"firstName == %@", @"Steve"]];

I only need one version of this method, so I decided to run each of them through some admittedly crude performance testing. Using my simple Person class, I created an array of many thousands of Persons containing randomly assigned names. I then timed the execution of my two category methods when searching for the first match on a given first name. Perhaps unsurprisingly, the performance of the method which uses a block trounced the method which first filters the array before returning the first object found.

It was an interesting exercise and will save me some typing when searching arrays in the future. Please note that because the test for a match uses an NSPredicate object, the objects in the array must be key-value coding compliant for the keys you want to use in the predicate.

I’m always open to better ways of doing things – let me know if you have other ideas for solving this little problem.

You can download the Xcode project containing the simple command line tool I built to test these techniques using the link below.

ArraySearch.zip

Author image
About Brice Wilson