Search code examples
iosobjective-ccore-datamagicalrecord

Core Data crash: Collection was mutated while being enumerated


My app seems to be crashing when I try to perform a fetch. I'm using magical records. The error message is:

Collection <__NSCFSet: 0x17005a1f0> was mutated while being enumerated.

To me this indicates that we're changing objects in the context while performing the fetch, but I'm new to this so I might be wrong.

Here's the code it's pointing at:

- (void) buildAndFetchFRCsInContext:(NSManagedObjectContext*)context{

[context performBlock:^{
    __unused NSDate* start = [NSDate date];

    self.contactsFRC = [self buildFetchResultsControllerForClass:[Contact class] sortedBy:@"id" withPredicate:nil inContext:context];
    self.callsFRC = [self buildFetchResultsControllerForClass:[Call class] sortedBy:@"id" withPredicate:nil inContext:context];
    self.newsItemsFRC = [self buildFetchResultsControllerForClass:[NewsItem class] sortedBy:@"id" withPredicate:nil inContext:context]; 

    NSError* error;

    // Peform the fetches
    [self.contactsFRC performFetch:&error];
    [self.callsFRC performFetch:&error];
    [self.newsItemsFRC performFetch:&error]; //Crash points to this line
    NSLog(@"Spent [%@s] performing fetchs for counts!", @(fabs([start timeIntervalSinceNow])));

    [self calculateAndBroadcastCounts];
}];
}

The context being passed in is:

- (instancetype) initWithUserSession:(BPDUserSession*)userSession{
    self = [super init];
    ...
    self.context = [NSManagedObjectContext MR_context];
    [self buildAndFetchFRCsInContext:self.context];
    ...
}

What I think is that this class is being initialized in the main thread, but performBlock adds the block to a queue and then executes from a different thread. But I don't think this is true because the purpose of performBlock is to perform that block on another thread.

From what I've posted, can anyone tell what the issue is?

Update:

I tried moving the buildFetchResultsController call to outside of the perform block:

- (void) buildAndFetchFRCsInContext:(NSManagedObjectContext*)context{
self.contactsFRC = [self buildFetchResultsControllerForClass:[Contact class] sortedBy:@"id" withPredicate:nil inContext:context];
self.callsFRC = [self buildFetchResultsControllerForClass:[Call class] sortedBy:@"id" withPredicate:nil inContext:context];
self.newsItemsFRC = [self buildFetchResultsControllerForClass:[NewsItem class] sortedBy:@"id" withPredicate:nil inContext:context];

NSMutableArray *list = [[NSMutableArray alloc] initWithCapacity:100];
for (int i = 0; i < 100; i++) {
    list[i] = [self buildFetchResultsControllerForClass:[NewsItem class] sortedBy:@"id" withPredicate:nil inContext:context];
}

[context performBlock:^{
    __unused NSDate* start = [NSDate date];

    NSError* error;

    // Peform the fetches
    [self.contactsFRC performFetch:&error];
    [self.callsFRC performFetch:&error];
    [self.newsItemsFRC performFetch:&error];

    for (int i = 0; i < list.count; i++) {
        [list[i] performFetch:&error]; // Generally error is thrown on i = 5 ~> 10
    }

    NSLog(@"Spent [%@s] performing fetchs for counts!", @(fabs([start timeIntervalSinceNow])));

    [self calculateAndBroadcastCounts];
}];
}

but this still fails. I'm able to reproduce the failure with the loop shown above. I also tried creating a new context to use from within the actual performBlock closure with NSPrivateQueueConcurrencyType but this didn't work either, same issue.

Note: I'm using MagicalRecords, so for those of you who aren't familiar, [NSManagedObjectContext MR_context]; is equivalent to the context returned from:

NSManagedObjectContext *context = [[self alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; // Here self is NSManagedObjectContext
[context setParentContext:parentContext];
[context MR_obtainPermanentIDsBeforeSaving];
return context;

Solution

  • My issue was I was performing a save on a context and that save had concurrency issues, I was trying to save objects created on the main thread from a thread that wasn't main. Moving that code to the main thread fixed the crash.