Multithreading violation core data - ios

I have an app where I download the data on startup using a list of operations and it crashes randomly for unknown core data reasons so I spent few days on checking the best practices to update/fetch data in multithreading core data with MagicalRecord. One of the options was to enable the multithreading debugger -com.apple.CoreData.ConcurrencyDebug 1 where Xcode stops the apps when it violates one of their rules. So, Xcode stops my app on this line [SyncRequestEntity MR_createEntityInContext:[self getPrivateContext]]
+ (MagicalRecordVersionNumber) version
{
return MagicalRecordVersionNumber2_3;
}
#implementation NSManagedObjectContext (MagicalRecord)
+ (NSManagedObjectContext *) MR_context
{
return [self MR_contextWithParent:[self MR_rootSavingContext]];
}
+ (NSManagedObjectContext *) MR_contextWithParent:(NSManagedObjectContext *)parentContext
{
NSManagedObjectContext *context = [self MR_newPrivateQueueContext];
[context setParentContext:parentContext];
[context MR_obtainPermanentIDsBeforeSaving];
return context;
}
- (void) MR_obtainPermanentIDsBeforeSaving
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(MR_contextWillSave:)
name:NSManagedObjectContextWillSaveNotification
object:self];
}
+ (NSManagedObjectContext *) MR_newPrivateQueueContext
{
NSManagedObjectContext *context = [[self alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
MRLogInfo(#"Created new private queue context: %#", context);
return context;
}
#end
#implementation MyClass
- (NSManagedObjectContext *) getPrivateContext
{
if (self.privateContext == nil)
{
self.privateContext = [NSManagedObjectContext MR_context];
}
return self.privateContext;
}
- (SyncRequestEntity *) getSyncRequest
{
SyncRequestEntity *syncRequest = [SyncRequestEntity MR_findFirstByAttribute:#"key" withValue:self.itemKey inContext:[self getPrivateContext]];
// Checking if the entity was sync previously with the same filters.
if (syncRequest == nil)
{
syncRequest = [SyncRequestEntity MR_createEntityInContext: [self getPrivateContext]];
}
return syncRequest;
}
#end
#implementation NSManagedObject (MagicalRecord)
+ (id) MR_createEntityInContext:(NSManagedObjectContext *)context
{
if ([self respondsToSelector:#selector(insertInManagedObjectContext:)] && context != nil)
{
id entity = [self performSelector:#selector(insertInManagedObjectContext:) withObject:context];
return entity;
}
else
{
NSEntityDescription *entity = nil;
if (context == nil)
{
entity = [self MR_entityDescription];
}
else
{
entity = [self MR_entityDescriptionInContext:context];
}
if (entity == nil)
{
return nil;
}
return [[self alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
}
}
#end
The privateContext is a local variable for each operation so I have private contexts for each operation in order to not interrupt the main one. The point is that I create one private context for each thread and I'm just trying to create a new NSManagedObject instance using this context and Xcode says that I'm violating the multithreading core data rules. Does anyone have any clue on what's happening?

We have the same issue when developing our own app.
When you try to perform a write operation in a thread different to the context one, it crash sometimes.
Our solution was to make a private manager on the AppDelegate.m file. Just add this code:
- (NSManagedObjectContext *)getPrivateManagedObjectContext
{
if (self.managedObjectContext != nil) {
return self.managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self getPersistentStoreCoordinator];
if (coordinator != nil) {
self.managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return self.managedObjectContext;
}
Then when you need to perform any operation, you should use this method that ensures the block is running on the same thread of the context:
[self.managedObjectContext performBlock:^{...}];
[self.managedObjectContext performBlockAndWait:^{...}];

Related

Getting and using NSManagedObject in different threads

I have problem with architecting my code.
I have view that create and depend on model class. Model class is responsible for all calculations and logic. This mean that it has a lot of things to calculate, and getting object from Core data.
Regarding enter link description here, every NSManagedObject that is obtained in one thread, need to be used in same thread.
My problem is that on create object need to be obtained in different thread, because it take some time to build model, but after this, I need to obtain object and they properties in main thread from View (for example in cellForIndex...)
I know, I am not the only one with this design. How others solve this problem?
EDIT :
To concrete this with code.
Let say we have UIView object MyUIView and model object Model
MyUIView
#interface MyUIView ()
#property(nonatomic) Model * model;
#end
#implementation MyUIView
- (void) createModel
{
// privateManagerContext is context manager created with
// NSPrivateQueueConcurrencyType, connected to persistent store
// and sync with "main thread" manager context with
// NSManagedObjectContextDidSaveNotification and NSManagedObjectContextDidSaveNotification
[self.model createWithManagadContext:privateManagerContext];
}
// ASSUME THAT THIS CODE IS CALLED AFTER
- (void) getNumberOfSomeProperties
{
int number = [self.model getNumberOfProperties];
}
- (void) getProperties
{
NSArray *array = [self.model properties]
}
// OR WE HAVE TO TRIGGERED SOME LONG CALCULATION
- (void) triggerLongCalculation
{
[self.model longCalculation];
}
- (void) afterNotifyModelIsCompletedCalculating
{
[self doSomeWork];
[self getProperties];
....
}
#end
Model
#interface Model ()
#property(nonatomic) NSArray * cashedProperties;
#end
#implementation MyUIView
- (void) createWithManagadContext:(NSManagedObjectContext *) privateManagerContext
{
dispatch_async(self.model_queue, ^(void){
// Here we fetch objects and do some calculations
[self populateModel];
/// Model is complete
[Notifications notifyModelIsCompletedCreating:self];
});
}
- (void) longCalculation
{
dispatch_async(self.model_queue, ^(void){
// NO CORE DATA FETCH INVOLVED
[Notifications notifyModelIsCompletedCalculating:self];
});
}
- (int) getNumberOfProperties
{
return self.cashedProperties.count;
}
- (NSArray) properties
{
NSMutableArray * a = [[NSMutableArray alloc]init];
for (Property * p in self.cashedProperties) {
[a addObject:p.name];
}
return a;
}
#end
So in this hypothetical classes, how would you handle all NSManagedObject and NSManagedObjectContext ?
EDIT 2 :
I am using pattern where I create two managed object context in appdelegate, one private and one main, and create sync between them.
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil) {
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
- (NSManagedObjectContext * ) privateQueueContext
{
if (_privateQueueContext != nil) {
return _privateQueueContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_privateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateQueueContext setPersistentStoreCoordinator:coordinator];
}
return _privateQueueContext;
}
- (id)init
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(contextDidSavePrivateQueueContext:)
name:NSManagedObjectContextDidSaveNotification
object:[self privateQueueContext]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(contextDidSaveMainQueueContext:)
name:NSManagedObjectContextDidSaveNotification
object:[self managedObjectContext]];
}
return self;
}
#pragma mark - Notifications
- (void)contextDidSavePrivateQueueContext:(NSNotification *)notification
{
#synchronized(self) {
[self.managedObjectContext performBlock:^{
NSArray* objects = [notification.userInfo valueForKey:NSUpdatedObjectsKey];
for (NSManagedObject* obj in objects) {
NSManagedObject* mainThreadObject = [self.managedObjectContext objectWithID:obj.objectID];
[mainThreadObject willAccessValueForKey:nil];
}
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
}
- (void)contextDidSaveMainQueueContext:(NSNotification *)notification
{
#synchronized(self) {
[self.privateQueueContext performBlock:^{
NSArray* objects = [notification.userInfo valueForKey:NSUpdatedObjectsKey];
for (NSManagedObject* obj in objects) {
NSManagedObject* mainThreadObject = [self.privateQueueContext objectWithID:obj.objectID];
[mainThreadObject willAccessValueForKey:nil];
}
[self.privateQueueContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
}
You could use two different NSManagedObjectContext with parent/child configuration. The parent for the UI, in the main queue, the child for heavy work in the private queue. Once the child context finishes to perform his heavy work would save, its changes are then propagated to the main context. You could use in the case you're using a table view in your view controller a NSFetchedResultsController that observe on the main context. Once the main context receives and merge the changes from his child context, the NSFetchedResultsController would update the UI accordingly, as long as its delegate methods are implemented.
If you don't use NSFRC you could register your main context for notifications "name:NSManagedObjectContextObjectsDidChangeNotification" or "name:NSManagedObjectContextObjectsDidSaveNotification" and see what objects have been added/deleted/updated and update the UI consequently.
If you use thread confinement instead (made obsolete by Apple in the last years) of parent/child you may want to pass the objectID between threads and fetch the object in the context you need it as NSManagedObjects are not thread-safe.
Code:
First I wouldn't have a reference of your model in the view. View should be just dumb and expose outlets or methods to be populated. I would have a ViewController communicating with the model and the view.
Lets say you have a util method to create a worker context (as child context of your parent in the main queue) every time you need to perform a heavy job.
func newWorkerContext() -> NSManagedObjectContext {
let workerContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
workerContext.parentContext = self.mainContext
return workerContext
}
In your model you would have
//Lets say you have a reference to your workerContext
func longCalculation() {
workerContext.performBlock({ () -> Void in
//Heavy process of information
//Once finished you save the worker context and changes are propagated to the parent context
})
}
In your ViewController you would have
class MyViewController: UIViewController {
func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(handleDataModelChange(_:)), name: NSManagedObjectContextObjectsDidChangeNotification, object: REFERENCE_TO_MAIN_CONTEXT)
}
func deinit {
NSNotificationCenter.defaultCenter().removeObserver(self, name: NSManagedObjectContextObjectsDidChangeNotification, object: REFERENCE_TO_MAIN_CONTEXT)
}
func handleDataModelChange(notification: NSNotification) {
//Check changes are relevant for the UI you are updating and if so update.
if let changedObjects = changes[NSUpdatedObjectsKey] as? NSSet {
}
if let insertedObjects = changes[NSInsertedObjectsKey] as? NSSet {
}
if let deletedObjects = changes[NSDeletedObjectsKey] as? NSSet {
}
}
}
Remember that to persist the changes in the persistence store you have to save on the main context.
It's a simple example but I hoped it gives you the idea on what to do now.

iOS - update multiple CoreData entities & contexts

I am using CoreData in my app and I have two main entities, let's say Cats and Dogs.
I have this method that I use to initialise my context:
- (NSManagedObjectContext *)managedObjectContext
{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
It refers to this method in my App Delegate:
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
Now, at some point, I have to update some data in my Cats entity.
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Cats"];
NSMutableArray *catsArrayData = [[context executeFetchRequest:fetchRequest error:nil] mutableCopy];
NSManagedObject *aCat = [catsArrayData objectAtIndex:i];
NSMutableArray *catsArray = [aCat valueForKey:#"cat"];
[catsArray addObject:responseData];
[aCat setValue:catsArray forKey:#"cat"];
NSError *error = nil;
[context save:&error];
Then, I want to do the same for my Dogs entity. So I use the same code, but for the dogs entity. It's in another part of my code, so I have to redefine:
NSManagedObjectContext *context = [self managedObjectContext];
But the problem is that even though I save this second context, it is actually not saved. The data isn't there. How can I resolve this issue?
You need implement your own change notification mechanism, This is good source to learn it. Multi-Context CoreData.

NSManagedObjectContext giving an error asking for a ";" and I'm not sure why

When in Xcode 5 I am using the code below, which is in the #interface area, to get data from my CoreData model. It is telling me that I should insert a semi-colon here,
- (NSManagedObjectContext *)managedObjectContext;
Any help is appreciated!
Below is my full code block.
//Get Data
- (NSManagedObjectContext *)managedObjectContext{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
When in Xcode 5 I am using the code below, which is in the #interface area, to get data from my CoreData model.
This is you problem, you're only supposed to but declarations in the #interface. Actual implementations go in the #implementation section.
Your interface goes
#interface
- (NSManagedObjectContext *)managedObjectContext;
#end
In you implementation you put the actual method:
#implementation
...
//Get Data
- (NSManagedObjectContext *)managedObjectContext{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate respondsToSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
...
#end
UPDATE:
Thanks to Abizern for noticing that there was another bug in your code: the line [delegate performSelector:#selector(managedObjectContext)] should actually be [delegate respondsToSelector:#selector(managedObjectContext)], because otherwise the application will crash if it doesn't respond to the selector.
you have weird getter in my opinion, try ti use something like this one
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil)
{
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}

core data: background context ios

I already posted this problem and no one tell me what is the problem, please help.
I have problem when I use Core Data to save data in my iOS app.
My application can receive many and many text message in a second from a server.
So i need to save those message in my data base, now i use NSOperations inside queue to save messages (so you can imagine the number of the NSOperation in the NSOperationQueue).
The problem is if the i have a normal flow of messages then it works fine and if have an important flow of messages , the app freeze indefinitely or the NSOperations increase without limit in the queue.
PS: if remove the observer of merge the messages are saved without any problem
This my code of the NSOperation:
#interface SaveRecievedMessageOperation()
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong) AppDelegate *appdelegate;
#property (nonatomic, assign) BOOL executing;
#property (nonatomic, assign) BOOL finished;
#end
#implementation SaveRecievedMessageOperation
#synthesize message;
#synthesize managedObjectContext;
#synthesize appdelegate;
#synthesize executing;
#synthesize finished;
- (id)initWithMessage:(SipMessage *)messageToSave
{
if (self = [super init]) {
self.message = messageToSave;
}
return self;
}
- (void)main
{
#autoreleasepool
{
self.appdelegate = [[UIApplication sharedApplication] delegate];
[self managedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:self.managedObjectContext];
[self saveMessage];
}
}
- (NSManagedObjectContext *)managedObjectContext
{
if (managedObjectContext != nil)
{
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self.appdelegate persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[managedObjectContext setPersistentStoreCoordinator:coordinator];
[managedObjectContext setMergePolicy:NSOverwriteMergePolicy];
}
return managedObjectContext;
}
- (void)saveMessage
{
NSDictionary *header = self.message.headers;
NSArray *bodies = self.message.bodies;
SipBody *sipBody;
NSDictionary* body;
NSData *ContentData;
if ([[header valueForKey:#"Content-Type"] rangeOfString:#"application/json"].location != NSNotFound)
{
sipBody = [bodies objectAtIndex:0];
body = [NSJSONSerialization
JSONObjectWithData:sipBody.content
options:NSJSONReadingAllowFragments
error:nil];
}
else if ([[header valueForKey:#"Content-Type"] rangeOfString:#"multipart/mixed"].location != NSNotFound)
{
for (SipBody *sipB in bodies) {
if ([[sipB.headers valueForKey:#"Content-Type"] rangeOfString:#"application/json"].location != NSNotFound)
{
body = [NSJSONSerialization
JSONObjectWithData:sipB.content
options:NSJSONReadingAllowFragments
error:nil];
}
else
{
ContentData = [NSData dataWithData:sipB.content];
}
}
}
else
{
return;
}
MGMPhone *sender;
NSArray *senders = [self updatePhonesFromMSISDNsList:[[header valueForKey:#"swfrom"] componentsSeparatedByString:MGMseparator]];
sender = senders[0];
NSError *error;
MGMMessage *aMesage = [MGMMessage createInContext:self.managedObjectContext];
[aMesage setBoxType:[NSNumber numberWithInteger:BoxTypeIncomingMessage]];
[aMesage setContent:[body valueForKey:#"Content"]];
[aMesage setContentType:[header valueForKey:#"Content-Type"]];
[aMesage setGroupMessage:( [[header valueForKey:#"groupmessage"] isEqualToString:#"true"]
?
[self saveContext];
}
#pragma mark core data
- (void)mergeChanges:(NSNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
NSManagedObjectContext *mainContext = [appdelegate managedObjectContext];
[mainContext performSelector:#selector(mergeChangesFromContextDidSaveNotification:) withObject:notification];
[self setManagedObjectContext:nil];
}
- (void)saveContext
{
NSError *error = nil;
if (self.managedObjectContext != nil)
{
if ([self.managedObjectContext hasChanges]) {
BOOL isSaved = [self.managedObjectContext save:&error];
if(!isSaved){
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
else if (![self.managedObjectContext hasChanges]){
[self setManagedObjectContext:nil];
}
}
}
#end
I think everything is fine, except the merge operation. I'm using very similar CoreData operations in background myself.
Typically you want to merge updates from background operations with the 'main' managed object context. So basically in the background operation you should only perform the save: method. (So don't register for save notifications nor initiate merge actions).
Unless you really need to merge managed object contexts in background threads, the save notification should be registered only once in the main thread (typically in a singleton e.g. appDelegate or your own singleton). The save notification will be sent by CoreData after any save operation in other contexts, allowing you to merge these changes with the main context.

iOS 5 Core Data duplicate row with multiple NSManagedObjectContext

Our table view controllers use an NSFetchedResultsController to show data from Core Data. We download new data in the background. When an entity is modified in the new data, on iOS 5.1.1 phone, we see that treated as a new row in the table instead of an update. Cannot duplicate on the iOS 5.1 simulator or an iOS 6 device.
The UIApplicationDelegate creates a NSManagedObjectContext with concurrency type NSMainQueueConcurrencyType. Our UITableViewController implements NSFetchedResultsControllerDelegate. In viewWillAppear we go fetch new data. In the method getting data, we create a second NSManagedObjectContext with concurrenty Type NSPrivateQueueConcurrencyType. We do a performBlock on that new context, and do the network call and json parsing. There's a NSFetchRequest to get the previous data, so we can delete the old objects, or modify any existing entities with the same id. After modify the existing entity or creating new ones, we then deleteObject the old entity objects. Then we save this private context. Then on the parent context, do a performBlock to save the changes there.
On iOS5.1, the table is incorrect. If we change on of the objects, instead of being modified, it is added to the table as a new row. If we leave this controller and come back to it, getting new data, it shows the right amount.
AppDelegate.m
- (void)saveContext
{
[self.privateWriterContext performBlock:^{
NSError *error = nil;
[self.privateWriterContext save:&error];
// Handle error...
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.privateWriterContext];
}];
}
#pragma mark - Core Data stack
- (NSManagedObjectContext *)privateWriterContext
{
if (__privateWriterContext != nil) {
return __privateWriterContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__privateWriterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[__privateWriterContext setPersistentStoreCoordinator:coordinator];
}
return __privateWriterContext;
}
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil) {
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[__managedObjectContext setParentContext:self.privateWriterContext];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(saveContext:)
name:NSManagedObjectContextDidSaveNotification
object:__managedObjectContext];
return __managedObjectContext;
}
class that fetches from server
+ (void) fetchFromURL:(NSString *) notificationsUrl withManagedObjectContext (NSManagedObjectContext *)managedObjectContext
{
NSManagedObjectContext *importContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
importContext.parentContext = managedObjectContext;
[importContext performBlock: ^{
NSError *error;
NSURLResponse *response;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSData *responseData = [NSData dataWithContentsOfURLUsingCurrentUser:[NSURL URLWithString:notificationsUrl] returningResponse:&response error:&error];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSMutableSet *newKeys = [[NSMutableSet alloc] init];
NSArray *notifications;
if(responseData) {
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSMutableDictionary *previousNotifications = [[NSMutableDictionary alloc] init];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Notification"];
NSArray * oldObjects = [importContext executeFetchRequest:request error:&error];
for (Notification* oldObject in oldObjects) {
[previousNotifications setObject:oldObject forKey:oldObject.notificationId];
}
notifications = [json objectForKey:#"notifications"];
//create/update objects
for(NSDictionary *notificationDictionary in notifications) {
NSString *notificationId = [notificationDictionary objectForKey:#"id"];
Notification *notification = [previousNotifications objectForKey:notificationId];
if(notification) {
[previousNotifications removeObjectForKey:notificationId];
} else {
notification = [NSEntityDescription insertNewObjectForEntityForName:#"Notification" inManagedObjectContext:importContext];
[newKeys addObject:notificationId];
}
notification.notificationId = [notificationDictionary objectForKey:#"id"];
//other properties from the json response
}
for (NSManagedObject * oldObject in [previousNotifications allValues]) {
[importContext deleteObject:oldObject];
}
}
if (![importContext save:&error]) {
NSLog(#"Could not save to main context after update to notifications: %#", [error userInfo]);
}
//persist to store and update fetched result controllers
[importContext.parentContext performBlock:^{
NSError *parentError = nil;
if(![importContext.parentContext save:&parentError]) {
NSLog(#"Could not save to store after update to notifications: %#", [error userInfo]);
}
}];
}
];
}
I suffered the problem recently too.
The problem is because of two contexts in different threads.
On device which is running iOS 5.1, merging them cause it to insert a new record instead of update it. I change the background thread to use main context instead and the problem is gone.
No clue why merging does not work in this particular case.

Resources