I am working with MagicalRecord 2.2. Sometimes I receive 'CoreData could not fulfill a fault' crash in production code and I've created some test code to investigate this problem. Here is its description.
I have an entity "Object". At the start, I create one object on main thread, then I call [MagicalRecord saveWithBlock:...] where I fetch this object from local context and delete it. I added extra logging to the method (NSManagedObjectContext+MagicalSaves.m)
(void)MR_saveWithOptions:( MRSaveContextOptions)mask completion
// BEFORE REAL SAVE is placed before line 'saved = [self save:&error];'
// AFTER REAL SAVE is placed after it
// Logging code line is:
//
// NSManagedObjectContext *context = ... (defaultContext/rootContext/self)
// NSArray *objects = [context executeFetchRequest:[NSFetchRequest fetchRequestWithEntityName:@"Object"] error:NULL];
// NSLog(@"%i objects in %@ context (%@)",
// [objects count],
// context == [NSManagedObjectContext defaultContext] ? @"main" :
// context == [NSManagedObjectContext MR_rootSavingContext] ? @"root" : @"local",
// [NSThread isMainThread] ? @"MAIN" : @"BACKGROUND");
2014-07-04 23:07:32.322 MyApp[3661:1703] BEFORE REAL SAVE // Current context is local
2014-07-04 23:07:32.325 MyApp[3661:1703] 1 objects in default context (BACKGROUND)
2014-07-04 23:07:32.325 MyApp[3661:1703] 1 objects in root context (BACKGROUND)
2014-07-04 23:07:32.326 MyApp[3661:1703] 0 objects in current context (BACKGROUND) // We deleted object before save, so all is ok
2014-07-04 23:07:32.327 MyApp[3661:1703] AFTER REAL SAVE // Line 'saved = [self save:&error];' was executed
2014-07-04 23:07:32.328 MyApp[3661:1703] 0 objects in default context (BACKGROUND) // Why is object deleted here? We didn't call save for parent context yet
2014-07-04 23:07:32.339 MyApp[3661:1703] 0 objects in root context (BACKGROUND) // Same here, why changes are immediately propagated to all contexts?
2014-07-04 23:07:32.340 MyApp[3661:1703] 0 objects in local context (BACKGROUND)
2014-07-04 23:07:32.340 MyApp[3661:1703] BEFORE REAL SAVE // This save was called for localContext.parentContext, so current context is root
2014-07-04 23:07:32.341 MyApp[3661:1703] 0 objects in default context (BACKGROUND)
2014-07-04 23:07:32.341 MyApp[3661:1703] 0 objects in root context (BACKGROUND) // Before save there are no objects in root context
2014-07-04 23:07:32.342 MyApp[3661:1703] 0 objects in root context (BACKGROUND)
2014-07-04 23:07:32.343 MyApp[3661:1703] AFTER REAL SAVE
2014-07-04 23:07:32.343 MyApp[3661:1703] 0 objects in default context (BACKGROUND)
2014-07-04 23:07:32.343 MyApp[3661:1703] 0 objects in root context (BACKGROUND)
2014-07-04 23:07:32.420 MyApp[3661:1703] 0 objects in root context (BACKGROUND)
Maybe I misunderstand something? Why is default context modified on background?
In production code I have several places, when I handle async events (initiated by performSelectorOnMainThread, NSNotification, NSTimer) and should delete some objects with entity X. I call "saveWithBlock", where delete needed instances and in completion block post notification to update UI.
When I handle notification for UI update, I have code:
NSArray *objects = [Object findAll]; // #1
NSPredicate *unreadPredicate = [NSPredicate predicateWithFormat:@"isRead == %@", [NSNumber numberWithBool:NO]]; // #2
NSArray *unreadObjects = [objects filteredArrayUsingPredicate:unreadPredicate]; // #3
'CoreData could not fulfill a fault' crash is happened at line #3.
It seems, that some activity modifies "objects", posts "Update UI" notification, and at the same time other activity deletes some "objects" again. After objects were fetched at line #1, defaultContext was modified on background and filtering fails. This crash happens very rarely, but it happens.
How can I handle this situation?