Core Data: NSObjectID and NSTemporaryObjectID leaks
Asked Answered
C

1

4

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.

Cytotaxonomy answered 4/8, 2015 at 9:35 Comment(2)
Downloaded project, ran it in instruments, and it did not see anything show up in leaks. XCode 6.4, deployment target 8.4, iPhone 6 simulator. I'd be willing to look, if you can provide something else to go on.Oriflamme
Jody thanks for looking into this. You should be able to reproduce it with these steps: (1) Open the project in Xcode 6.4, (2) Profile for allocations/leaks with the iPad 2 / iOS 8,4 simulator, (3) The leaks instrument won't detect any leaks, but when you filter on NSTemporaryObjectID or _NSObjectID you should see persistent instances that should have been deallocated (similar to the screenshot above, for the screenshot I used the Xcode 7 profiler).Cytotaxonomy
O
1

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).

Oriflamme answered 14/8, 2015 at 16:18 Comment(2)
Hi Jody, thanks for your answer! I'll try the obtainPermanentIDsForObject method. If you create a context that is directly connected to the persistent store, how do you notify other contexts of changes done in your context?Cytotaxonomy
Core Data will automatically send NSManagedObjectContextWillSaveNotification and NSManagedObjectContextDidSaveNotification to the default notification center anytime a context is saved. Likewise, NSManagedObjectContextObjectsDidChangeNotification will be sent anytime a change is made to a context. Lots of questions on SO and lots of information elsewhere on how to do this. If you find yourself stumped, post another question - hit me with a comment directing me and I'll take a look.Oriflamme

© 2022 - 2024 — McMap. All rights reserved.