Celebrating 10 Years!

profile picture

A Massive Speed Difference In iPhone CoreData

October 19, 2012 - Roundwall Software

It was brought to my attention last night by my good friend, Collin Donnel, that using a fetch request to grab objects based on their objectID’s was much slower than simply using a for loop and the NSManagedObjectContext method -existingObjectWithID:error: to grab each object one by one. My initial response was supreme disbelief. How could this be true? I needed a break today from my pile of work to do, so I thought I would investigate and see what’s up.

I started with a dummy project to create a scenario I could measure. You can find that project here. I just created a single-view app with 2 buttons, one to trigger a fetch with a predicate and one to trigger finding objects with a for loop. When the app launched, it creates 1000 objects to have some test data. Nothing fancy, a simple object with a few properties. The code for these two fetch methods are fairly straightforward as you can see here:

+ (NSArray *)fetchRequestobjectsWithIDs:(NSArray *)objectIDs inContext:(NSManagedObjectContext *)context
{
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:[self entityName]];
    [request setPredicate:[NSPredicate predicateWithFormat:@"SELF in %@", objectIDs]];
    
    NSError *fetchError = nil;
    NSArray *results = [context executeFetchRequest:request error:&fetchError];
    if(!results){
        NSLog(@"Error fetching: %@", fetchError);
    }
    return results;
}

+ (NSArray *)forLoopObjectsWithIDs:(NSArray *)objectIDs inContext:(NSManagedObjectContext *)context
{
    NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[objectIDs count]];
    [objectIDs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSError *findError = nil;
        NSManagedObject *object = [context existingObjectWithID:obj error:&findError];
        if(object){
            [objects addObject:object];
        }else{
            NSLog(@"Error finding object %@", findError);
        }
    }];
    return objects;
}

I ran this test project on my iPhone 4 (I know, so old) and was quite surprised by the results. The trace it generated looks like this:

The Speed Test Trace

The first bit of activity there is the app launching, and the 1000 objects getting created. Not interesting for the purpose of this article. That big spike there is the fetch request. In between those 2 spikes, the tiny bit you can barely see, is the for loop version. Amazing right? Which do you want in your app, that activity spike to fetch 1000 objects? That tiny bit of CPU usage you can hardly see on the timeline?

Now go forth and enjoy your faster app! Also, be sure to buy a copy of Collin’s app, Pinbook to say thanks for the tip.