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?

3 Answers 3

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];
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

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];
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.