问题
I mistakenly created a group of objects on a background thread, which created a new managedObjectContext in which the objects were saved.
This resulted in numerous "Core Data could not fulfill a fault" crashes in my live app. Please note - I am not trying to delete these objects - it is a problem with the way they were created, when I try to save them again afterwards in the app delegate (main) context on the main thread.
The thing that is confusing is that I am seeing this error for other objects, objects of a different type. They can be related to the objects created in the background thread, though they themselves were not created in the in the background thread.
I am confused as to how this could have happened. How could I get the "Core Data could not fulfill a fault" error for an object not created in the background thread, but the app delegate (main) context?
And is there any way at all to go back and fix this mistake in the live apps of my users?
Let me reference my other question, through which I discovered this error: "Core Data could not fulfill a fault" for objects that were not deleted
I wrote up a new question because I feel that it is a different issue - though most definitely related.
Here is the code that created the objects in the background thread:
- (void)friendPickerViewControllerDidChooseFriends:(NSArray *)friends {
__ENTERING_METHOD__
if (friends.count > 0) {
[[FacebookHelper sharedManager] friendPickerController].navigationController.navigationBar.userInteractionEnabled = NO;
[self startProgressIndicator];
[self performSelectorInBackground:@selector(importFriends:) withObject:friends];
}
else {
[self dismissModalImportViewControllerAnimated];//releases picker delegates, etc
}
}
#pragma mark -
#pragma mark Import Friend
- (void)importFriends:(NSArray*)friends {
__ENTERING_METHOD__
for (NSDictionary<FBGraphUser> *friend in friends) {
[self importFriend:friend withCompletion:^(void){
CGFloat friendNumber = [friends indexOfObject:friend]+1;
CGFloat friendCount = friends.count;
self.importProgress = friendNumber/friendCount;
}];
}
}
- (void)importFriend:(NSDictionary<FBGraphUser>*)friend withCompletion:( void (^) (void) )completionBlock {
__ENTERING_METHOD__
Person *myNewPerson = [GetObjectArrayHelper createNewPersonMocSaveNew:YES];
myNewPerson.facebookID = friend.id;
myNewPerson.facebookName = friend.name;
NSString *nameFirst = [friend.first_name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *nameLast = [friend.last_name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *imageID = friend.id;
UIImage *pickedImage = nil;
if(imageID){
pickedImage = [FacebookHelper imageForObject:imageID];
}
DLog(@"pickedImage:%@",pickedImage);
if(pickedImage){
[self setImagesForFacebookImage:pickedImage forPerson:myNewPerson];
}
//we should ALWAYS have a name
[Helper changePerson:myNewPerson firstName:nameFirst lastName:nameLast];
if(completionBlock) {
completionBlock();
}
}
- (void)finishedImporting {
__ENTERING_METHOD__
[SVProgressHUD showSuccessWithStatus:[self completeString]];
[self performSelector:@selector(dismissModalImportViewControllerAnimated) withObject:nil afterDelay:SV_PROGRESS_HUD_SUCCESS_DELAY];
}
- (void)dismissModalImportViewControllerAnimated {
__ENTERING_METHOD__
[Helper mocSave];//THIS SAVES IN THE APP DELEGATE MANAGED OBJECT CONTEXT -
[SVProgressHUD dismiss];
[self dismissViewControllerAnimated:YES completion:^(void){
[[FacebookHelper sharedManager] friendPickerController].delegate = nil;
[[FacebookHelper sharedManager] friendPickerController].navigationController.navigationBar.userInteractionEnabled = YES;
}];
}
Please note that the objects that I am concerned about are not any of the objects created here (or in methods called here), but objects that later become associated with these objects.
Why are THEY "Core Data could not fulfill a fault" crash? (I understand why any object created here or in a method called here would get it).
Also - once I figure out why this mess happened (and fix the code that caused it) I need to fix the rogue objects in my users' live apps. I could really use some advise on that as well!
回答1:
Based on your question and your comments, you are:
- Using a single managed object on multiple threads.
- Not taking any precautions to deal with the fact that
NSManagedObjectContext
is not thread safe.
As a result, you are getting weird, confusing crashes.
This is exactly what one would expect in this situation. Using a managed object context on more than one thread without taking precautions is essentially a recipe for crashes and confusion. These crashes may not make a lot of sense, because you're corrupting the context's internal state.
If you want to use a context on more than one thread, you must use one of the queue confinement options (NSMainQueueConcurrencyType
or NSPrivateQueueConcurrencyType
) and then you must put all code that uses the context or any object fetched from them in a performBlock:
or performBlockAndWait:
. (Exception: if you use NSMainQueueConcurrencyType
and you know your code is on the main thread, you may just use the context directly). You might also use your own locking mechanism via something like NSLock
, but come on, threading is already hard enough for most people.
It's generally better to use one context per thread, either as parent/child contexts or as independent contexts, but the approach above also works.
If you've actually managed to save bogus objects as a result of this, the only real recourse is to fetch those objects and either fix them up or delete them. How to identify these objects depends on your data model-- there's no universal test for "is this object crap?". Any checks depend on what your app considers to be correct.
来源:https://stackoverflow.com/questions/20006689/core-data-could-not-fulfill-a-fault-for-objects-that-were-created-in-the-appde