Search code examples
core-datamemory-leaksinstrumentsnsmanagedobjectcontext

Core Data: NSObjectID and NSTemporaryObjectID leaks


Before I send my app to the App Store I like to check it for memory leaks and other fishy stuff with instruments. There is one Core Data issue that I can't seem to solve, so I've decided to create a small test app to illustrate the problem.

What's the problem?

When I save an entity in a (child) NSManagedObjectContext it is propagated to its parent NSManagedObjectContext. During this process Core Data creates internal instances of _NSObjectID and NSTemporaryObjectID. For some reason these instances are left behind and the only way to get rid of them is to reset the parent NSManagedObjectContext.

My app is of course a lot more complex than this little test app and resetting the NSManagedObjectContext isn't an option for me.

Test app

The test app is a standard iOS app based on the single view template with the CoreData option checked. I've used objective-c to keep it similar to my production app.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Initialize the Core Data stack
    self.persistentStoreCoordinator = [self persistentStoreCoordinator];

    // Create the a private context
    self.rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    self.rootContext.persistentStoreCoordinator = self.persistentStoreCoordinator;

    // Create a child context
    self.childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    self.childContext.parentContext = self.rootContext;

    // Create a person
    [self.childContext performBlockAndWait:^{
        Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.childContext];
        person.name = @"John Smith";
        person.age = 30;

        // Save the person
        [self.childContext save:nil];

        // Save the root context
        [self.rootContext performBlockAndWait:^{
            [self.rootContext save:nil];
        }];
    }];

    return YES;
}

When you run the code above with instruments and the allocations instrument you can see that Core Data leaves some stuff behind.

You can find the full project here: https://github.com/Zyphrax/CoreDataLeak

Instruments

Things I've tried

I've tried things like [context refreshObject:... mergeChanges:YES], adding @autoreleasepool and/or [context processPendingChanges] inside the blocks, it all doesn't help. The only way to get it clean is to do a [context reset] (sledgehammer approach).

It's hard to find other people reporting this problem. This blog post seems similar:
http://finalize.com/2013/01/04/core-data-issues-with-memory-allocation/

I hope you guys can help me with this.


Solution

  • Here is what I see, which is very similar to yours...

    enter image description here

    However, I don't know that I would be concerned, unless you see lots of these, and they never go away. I assume the internals of Core Data (including the row cache has) some sort of object caching going on.

    On the other hand, my Core Data usage has changed a bit over the past year or two.

    Unless it is a very simple app, I almost never create new objects in a child context. I will fetch and modify them, but if I end up creating a new object, I make sure that is done in a sibling context.

    However, if you modify your code slightly, by adding this line (with your appropriate error handling - it returns BOOL) before the initial save...

    NSArray *inserted = self.childContext.insertedObjects.allObjects;
    [self.childContext obtainPermanentIDsForObjects:inserted error:&error];
    

    you should get something like this instruments report, which shows all objects created as being transient...

    enter image description here

    Thus, I don't necessarily think it is a permanent leak, because once I force the context to convert to a permanent ID, the objects go away. However, who knows how long they keep those object ID objects cached.

    In general, when I create objects in a context that contains a hierarchy, I will always obtain permanent IDs first (for many reasons). However, as I said earlier, I usually create new objects in a context that is directly created to the persistent store (because I have had to deal with other issues related to hierarchies temporary object IDs, especially when using multiple non related contexts).