views:

3969

answers:

4

I have an NSArray and I'd like to create a new NSArray with objects from the original array that meet certain criteria. The criteria is decided by a function that returns a BOOL.

I can create an NSMutableArray, iterate through the source array and copy over the objects that the filter function accepts and then create an immutable version of it.

Is there a better way?

+13  A: 

NSArray and NSMutableArray provide methods to filter array contents. NSArray provides filteredArrayUsingPredicate: which returns a new array containing objects in the receiver that match the specified predicate. NSMutableArray adds filterUsingPredicate: which evaluates the receiver’s content against the specified predicate and leaves only objects that match. These methods are illustrated in the following example.

NSMutableArray *array =
    [NSMutableArray arrayWithObjects:@"Bill", @"Ben", @"Chris", @"Melissa", nil];

NSPredicate *bPredicate =
    [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
NSArray *beginWithB =
    [array filteredArrayUsingPredicate:bPredicate];
// beginWithB contains { @"Bill", @"Ben" }.

NSPredicate *sPredicate =
    [NSPredicate predicateWithFormat:@"SELF contains[c] 's'"];
[array filterUsingPredicate:sPredicate];
// array now contains { @"Chris", @"Melissa" }
lajos
It might have been more appropriate simply to link to the documentation that this is copied from: http://developer.apple.com/documentation/Cocoa/Conceptual/Predicates/Articles/pUsing.html
mmalc
I listened to Papa Smurf's podcast and Papa Smurf said answers should live in StackOverflow so the community can rate and improve them.
willc2
@mmalc - Maybe more apporopriate, but certainly more convenient to view it right here.
Bryan
+1  A: 

As lajos said, there’s the NSPredicate mechanism. However, if your predicate can’t be conveniently expressed in terms of NSPredicate, the answer is no, there’s no better way than iterating built into Foundation. You might want to try a higher order messaging framework.

There are indications that future versions of Cocoa will support more convenient mechanisms for filtering and similar operations out of the box.

Ahruman
+3  A: 

Assuming that your objects are all of a similar type you could add a method as a category of their base class that calls the function you're using for your criteria. Then create an NSPredicate object that refers to that method.

In some category define your method that uses your function

@implementation BaseClass (SomeCategory)
- (BOOL)myMethod {
    return someComparisonFunction(self, whatever);
}
@end

Then wherever you'll be filtering:

- (NSArray *)myFilteredObjects {
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"myMethod = TRUE"];
    return [myArray filteredArrayUsingPredicate:pred];
}

Of course, if your function only compares against properties reachable from within your class it may just be easier to convert the function's conditions to a predicate string.

Ashley Clark
A: 

If you are OS X 10.6/iOS 4.0 or later, you're probably better off with blocks than NSPredicate. See -[NSArray indexesOfObjectsPassingTest:], or write your own category to add a handy -select: or -filter: method (example).

Clay Bridges