NSArray find object or objects - best practices

Uptown Apps picture Uptown Apps · Nov 16, 2013 · Viewed 44.5k times · Source

Solution: I have marked @BlackRider's answer as correct as it is the most versatile especially for complex comparisons however there are other very good answers and comments. I would encourage anyone with the same or similar question to review them and evaluate the best course of action for your specific situation.

In my situation, I am actually not using BlackRider's solution in my implementation. I have elected to use my own solution (see Edit #2 below) with help from @JoshCaswell's comments as well as @voromax's suggestion of indexesOfObjectsWithOptions:passingTest: due to the fact that my comparisons are very simple in this situation.

Thanks to everyone who answered and provided insight.


I am looking for an efficient way to retrieve an object from an NSArray based on a property of that object (a unique identifier, in this case). In C#.NET using Linq I would do something like

MyObject obj = myList.Single(o => o.uuid == myUUID);

I am also wondering if there is an efficient way to get an array of objects matching a non-unique property. Again, with Linq it would look like

List<MyObject> objs = myList.Where(o => o.flag == true).ToList();

Of course I can write loops to do this but they would not be reusable and I'm suspicious of their performance.

Finding an object with a unique ID:

-(MyObject*)findObjectWithUUID:(NSString*)searchUUID{
    for (MyObject* obj in _myArray){
        if([obj.uuid isEqualToString: searchUUID])
            return obj;
    }
}

Finding an array of objects:

-(NSArray*)findObjectsWithFlag:(BOOL)f{
    NSMutableArray* arr = [NSMutableArray array];
    for (MyObject* obj in _myArray){
        if(obj.flag == f)
            [arr addObject:obj];
    }
    return arr;
}

-- EDIT --

Luckily in the first situation the object I am looking for has a unique identifier and I know there will only be one. I came up with a solution to implement isEqual on my object which will be invoked by indexOfObject:

- (BOOL)isEqual:(id)object{
    return [self.uuid isEqualToString: ((MyObject*)object).uuid];
}

And then create a "fake" lookup object and use that to find the real one

MyObject *lookupObject = [[MyObject alloc] init];
lookupObject.uuid = searchUUID;
MyObject *actualObject = 
    [_myArray objectAtIndex:[_myArray indexOfObject:lookupObject]];

This is essentially the same as the for-in loop I posted above, but might be more readable & be more reusable. Of course, this only works for finding one unique object and does not address the second half of my question.

-- EDIT 2 --

Checking Class and implementing hash as recommended in comments.

- (BOOL)isEqual:(id)object{
    return [object isKindOfClass:[MyObject class]] && 
           [self.uuid isEqualToString: ((MyObject*)object).uuid];
}

- (NSUInteger)hash{
    return [self.uuid hash];
}

Answer

TotoroTotoro picture TotoroTotoro · Nov 16, 2013

You can use [NSPredicate], which gives you a query-like syntax for search. Check out this page for the predicate syntax description. Here's a simple example:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"propertyName == %@", @"value"];
NSArray *filteredArray = [myArray filteredArrayUsingPredicate:predicate];

As to performance, I think your solution is OK since any search in an array needs to iterate through all the elements anyway, and then, for each object, compare the value of a field against the value you search for. You can optimize repeat searches within the same data, e.g. by creating and populating a dictionary that maps values of some field to the matching objects (or collections of objects, if the mapping is one to many).