Generated NSManagedObject subclasses leak when re-creating Core Data stacks from scratch

Originator:eric.amorde
Number:rdar://FB7723824 Date Originated:June 3, 2020
Status:Open Resolved:
Product:Core Data Product Version:iOS 13
Classification:Application Slow/Unresponsive Reproducible:Yes
 
When creating and tearing down Core Data stacks from scratch, classes generated at run time are not released. This leads to significant memory bloat but also leads to a very slow lookup in a hash table containing the generated classes.

An example project that reproduces the issue has been attached.

This issue occurs in a unit test suite which re-creates the entire Core Data stack so each test has a clean slate. In large test bundles, the hash table grows large enough to cause the tests to slow down significantly.

General flow for reproducing this issue:

    NSURL *url = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@".momd"];
    for(int i = 0; i < 2000; i++) {
        NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:url];
        NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
        NSPersistentStore *store = [psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:nil];

        NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        context.persistentStoreCoordinator = psc;

        __unused MainEntity *obj = [NSEntityDescription insertNewObjectForEntityForName:@"MainEntity" inManagedObjectContext:context];
        [context save:NULL];

        [psc removePersistentStore:store error:NULL];
    }

Running the example project will run this code and display a prompt to open the Memory Graph Debugger to view the objects in question.

Example project: https://github.com/amorde/FB7723824-example

Comments

Adding this code seems to bring the performance back to what it was in iOS 12

Method m = class_getInstanceMethod([NSEntityDescription class], @selector(_addFactoryToRetainList:)); method_setImplementation(m, imp_implementationWithBlock(^void(id self, id _unused, int factory){ /// Do nothing }));

Overriding -[NSEntityDescription _addFactoryToRetainList:] to no-op brings us back to iOS 12 perf, but there’s still degraded performance as the number of iterations increases

By eric.amorde at June 8, 2020, 5:53 p.m. (reply...)

Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!