2

I have been told that I can use NSPredicate to duplicate the results of this method

- (void) clearArrayOut
{
    bool goAgain = false;

    for (int j=0; j<[array count]; j++)
    {
        if ([[array objectAtIndex:j] someMethod] == NO)
        {
            [array removeObjectAtIndex:j];
            goAgain = true;
            break;
        }
    }

    if (goAgain) [self clearArrayOut];
}

How can I make an NSPredicate that will filter an array based on the results of some method of a custom class's call?

Dustin
  • 6,783
  • 4
  • 36
  • 53

3 Answers3

10

To make a copy with the filter applied:

NSArray *filteredArray = [someArray filteredArrayUsingPredicate:
    [NSPredicate predicateWithBlock:^(id object, NSDictionary *bindings) {
        return [object someMethod]; // if someMethod returns YES, the object is kept
    }]];

To filter an NSMutableArray in place:

[someMutableArray filterUsingPredicate:
    [NSPredicate predicateWithBlock:^(id object, NSDictionary *bindings) {
        return [object someMethod]; // if someMethod returns YES, the object is kept
    }]];

But I would probably just use a for loop if I were filtering a small array. However, I'd write my for loop a little differently to avoid having to either decrement the index variable or call myself recursively:

- (void)clearArrayOut:(NSMutableArray *)array {
    for (int i = array.count - 1; i >= 0; --i) {
        if (![[array objectAtIndex:i] someMethod]) {
            [array removeObjectAtIndex:i];
        }
    }
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
1

You simply write it into your predicate, for example, lets assume you have an object with a method called isOdd and you want to filter your array to include only objects that return true for isOdd, you can do this:

#import <Foundation/Foundation.h>

@interface barfoo : NSObject 
{
    int number;
}

- (BOOL)isOdd;
- (id)initWithNumber:(int)number;

@end

@implementation barfoo

- (NSString *)description
{
    return [NSString stringWithFormat:@"%i", number];
}

- (BOOL)isOdd
{
    return (number % 2);
}
- (id)initWithNumber:(int)tnumber
{
    if((self = [super init]))
    {
        number = tnumber;
    }

    return self;
}

@end


int main(int argc, char *argv[]) {
    @autoreleasepool {
        NSMutableArray *array = [NSMutableArray array];
        for(int i=0; i<10; i++)
        {
            barfoo *foo = [[barfoo alloc] initWithNumber:i];
            [array addObject:[foo autorelease]];
        }

        NSLog(@"%@", array); // prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isOdd == true"]; // This is oure predicate. isOdd must be true for objects to pass
        NSArray *result = [array filteredArrayUsingPredicate:predicate];

        NSLog(@"%@", result);
    }
}

Of course this also works the other way around, your predicate could also read isOdd == false or you can add even more requirements for an object to pass. Eg isOdd == true AND foo == bar. You can read more about the NSPredicate syntax in the NSPredicate documentation.

JustSid
  • 25,168
  • 7
  • 79
  • 97
1

Your implementation is terribly inefficient to start with: rather than continuing the deletions recursively, you could change your loop to not advance if an object has been deleted, like this:

- (void) clearArrayOut {
    int j = 0;
    while (j < [array count]) {
        if ([[array objectAtIndex:j] someMethod] == NO) {
            [array removeObjectAtIndex:j];
        } else {
            j++;
        }
    }
}

You could do the same thing using filterUsingPredicate:, like this:

- (void) clearArrayOut {
    NSPredicate *p = [NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
        return [obj someMethod] == NO
    }];
    [array filterUsingPredicate:p];
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523