Core Data uniquing doesn't work? - objective-c

I'm having problems with duplicate objects of the same entity within a single context when using two managed object contexts.
Consider the following code:
[childMOC performBlockAndWait:^{
// CREATE PERSON IN CHILD MOC
Person *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person"
inManagedObjectContext:childMOC];
person.name = #"Ben";
// SAVE CHILD MOC TO PUSH CHANGES TO MAIN MOC
[childMOC save:nil];
NSManagedObjectID *personID = person.objectID;
[mainMOC performBlockAndWait:^{
// SAVE MAIN MOC TO PERSIST THE PERSON AND REPLACE ID TO PERMANENT
[mainMOC save:nil];
// GET THE PERSON IN THE MAIN MOC
Person *personInMainContext = (Person*)[mainMOC objectWithID:personID];
// GET THE PERSON'S NEW PERMANENT ID
NSManagedObjectID *personIdAfterSaveToPersistentStore = personInMainContext.objectID;
[childMOC performBlockAndWait:^{
// GET THE PERSON IN THE CHILD MOC WITH ITS NEW PERMANENT ID
// (this is common when sending the id from mainMOC to childMOC)
Person *samePersonFetchedFresh = (Person*)[childMOC objectWithID:personIdAfterSaveToPersistentStore];
// THE PERSON OBJECTS SHOULD BE EXACTLY THE SAME BECAUSE THE MOC GUARANTEES UNIQUING
samePersonFetchedFresh.name = #"Jerry";
NSLog(#"%# & %#", person.name, samePersonFetchedFresh.name);
// OUTPUT: Ben & Jerry
// NOT THE SAME?!
}];
}];
}];
This means that an object created in the child MOC loose its uniquing ability when it has been saved in the main MOC / persistent store.
Can anyone explain why uniquing doesn't work in this situation?

Try refreshing your person before logging its name. I think the uniquing works but cache invalidation might not.

Somewhere, you should listen to the main context NSManagedObjectContextDidSaveNotification, and propagate the changes to the child context(es) using mergeChangesFromContextDidSaveNotification.
If a complete merge do not suits your needs, you can also update each entity individually with refreshObject:mergeChanges:.
One way or another, you have to propagate the changes if you do not want to deal with up to date instances and old copies.
This is not done automatically, and this is for the good as the operation can be quite heavy. You have to handle the merges when appropriate, for the relevant contextes. Per example if you're populating your database using some JSON service, you'll use a context to add entities to the database, and you'll want to update the UI when the data is consistent and not each time you save something to the main context and maybe to the disk as well. Likewise, that context is most certainly not interested in the changes made in the UI then saved to the main context / disk. So then, the process is not automatic.

My solution was to:
1) Use the background MOC as the parent MOC and the main MOC as a child. As a bonus I don't need to save the main MOC to get the permanent IDs.
[DC.backgroundMOC performBlock:^{
// Add, save and update managed objects
[DC saveContext]; // The changes is being pushed to the main context
}];
2) Use NSManagedObjectContextDidSaveNotification to keep the main MOC up to date (the main MOC is updating the UI)
- (void) backgroundMOCSaved:(NSNotification*)notification {
[mainMOC performBlock:^{
[mainMOC mergeChangesFromContextDidSaveNotification:notification];
}];
}

Related

NSFecthedResultsController and NSPredicate update issue [duplicate]

I am presenting table view contents using NSFetchedResultsController which has a predicate:
[NSPredicate predicateWithFormat:#"visible == %#", [NSNumber numberWithBool:YES]]
On background thread using separate NSManagedObjectContext I update few of the entities and change theirs visible value from NO to YES. Save, merge changes in main thread's NSManagedObjectContext. But NSFetchedResultsController's fetchedObjects doesn't change. Also controller doesn't call -controller:didChangeObject:... on delegate. If entities are updated on main thread in identical manner (my test app calls the same method), everything works as expected.
Also Notification's NSUpdatedObjectsKey contains those objects.
Currently the only solutions I've found is to call for each of NSUpdatedObjectsKey entities:
NSManagedObjectContext *context = ... // main thread context
[context existingObjectWithID:[object objectID] error:nil]
This issue is only with updated objects which previously didn't match the predicate.
Am I missing something obvious?
Turns out main NSManagedObjectContext didn' t event fire NSManagedObjectContextObjectsDidChangeNotification for updated objects because it is not done for faulted objects.
Generic fix (or keep a track of object IDs that needs this treatment):
NSManagedObjectContext *context = [self managedObjectContext];
for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) {
[[context objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[context mergeChangesFromContextDidSaveNotification:notification];
From NSManagedObject Class Reference:
You can invoke this method with the
key value of nil to ensure that a
fault has been fired, as illustrated
by the following example.
You have to call processPendingChanges on your Background-NSManagedObjectContext after you merged the changes from an other NSManagedObjectContext.
See CoreData Programming Guide:
Note that the change notification is sent in NSManagedObjectContext’s
processPendingChanges method. The main thread is tied into the event
cycle for the application so that processPendingChanges is invoked
automatically after every user event on contexts owned by the main
thread. This is not the case for background threads—when the method is
invoked depends on both the platform and the release version, so you
should not rely on particular timing. If the secondary context is not
on the main thread, you should call processPendingChanges yourself at
appropriate junctures.

CoreData import not caught by NSFetchedResultsController [duplicate]

I am presenting table view contents using NSFetchedResultsController which has a predicate:
[NSPredicate predicateWithFormat:#"visible == %#", [NSNumber numberWithBool:YES]]
On background thread using separate NSManagedObjectContext I update few of the entities and change theirs visible value from NO to YES. Save, merge changes in main thread's NSManagedObjectContext. But NSFetchedResultsController's fetchedObjects doesn't change. Also controller doesn't call -controller:didChangeObject:... on delegate. If entities are updated on main thread in identical manner (my test app calls the same method), everything works as expected.
Also Notification's NSUpdatedObjectsKey contains those objects.
Currently the only solutions I've found is to call for each of NSUpdatedObjectsKey entities:
NSManagedObjectContext *context = ... // main thread context
[context existingObjectWithID:[object objectID] error:nil]
This issue is only with updated objects which previously didn't match the predicate.
Am I missing something obvious?
Turns out main NSManagedObjectContext didn' t event fire NSManagedObjectContextObjectsDidChangeNotification for updated objects because it is not done for faulted objects.
Generic fix (or keep a track of object IDs that needs this treatment):
NSManagedObjectContext *context = [self managedObjectContext];
for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) {
[[context objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[context mergeChangesFromContextDidSaveNotification:notification];
From NSManagedObject Class Reference:
You can invoke this method with the
key value of nil to ensure that a
fault has been fired, as illustrated
by the following example.
You have to call processPendingChanges on your Background-NSManagedObjectContext after you merged the changes from an other NSManagedObjectContext.
See CoreData Programming Guide:
Note that the change notification is sent in NSManagedObjectContext’s
processPendingChanges method. The main thread is tied into the event
cycle for the application so that processPendingChanges is invoked
automatically after every user event on contexts owned by the main
thread. This is not the case for background threads—when the method is
invoked depends on both the platform and the release version, so you
should not rely on particular timing. If the secondary context is not
on the main thread, you should call processPendingChanges yourself at
appropriate junctures.

Hang in mergeChangesFromContextDidSaveNotification (and merge conflicts)

In summary: My app is hanging when I call [myMOC mergeChangesFromContextDidSaveNotification:notification] in a multithreaded scenario.
The detailed situation is this:
My app downloads a whole bunch of data from a server and stores it in Core Data on first launch. It comes in several parts. Parsing the whole thing takes several seconds, and most of that time is spent on two of the chunks. So in order to speed it up, I've parallelized those two chunks. This is what it looks like:
NSArray *arr = [self parseJsonData:downloadedNSData]; //turns NSData into JSON array
//using NSJSONSerialization
NSMutableArray __block *first = [[NSMutableArray alloc]init];
NSMutableArray __block *second = [[NSMutableArray alloc]init];
//put half of arr in first and half in second with a loop
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dipatch_group_create();
dispatch_group_async(group, queue, ^
{
for (id element in first)
{
[MyDataClass parseData:element]; //create NSManagedObject subclass, save
}
[self saveContext];
});
dispatch_group_async(group, queue, ^
{
for (id element in second)
{
[MyDataClass parseData:element]; //create NSManagedObject subclass, save
}
[self saveContext];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
[self saveContext] calls save on the MOC. This is of course done on multiple threads, so I need to create a separate ManagedObjectContext for each thread. This is accomplished by exposing the MOC via a property on this object (self) that maintains an NSMutableDictionary of thread names (call description on NSThread) to NSManagedObjectContexts. When accessed, if there isn't a MOC for [NSThread currentThread], it creates a new one, adds it to the dictionary, and stores it. When each MOC is created, I subscribe to its change notifications:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification object:createdMOC];
In mergeChanges, I loop through my dictionary of contexts and call mergeChangesFromContextDidSaveNotification on all of them except the one for the thread that this is happening on. More specifically, I use [performSelector:onThread:withObject:waitUntilDone:] to have each MOC do this on the thread it's created for.
I'm also locking using NSLock around both the [myMOC save] and my mergeChanges method. If I didn't lock around those, I got "Cocoa error 133020", which apparently means an error merging changes.
So, this is what my logging tells me is happening:
Thread 1 acquires the lock to save and begins saving
A merge context notification comes on thread 1. Thread 1 acquires the lock for merging changes and begins that process. It merges changes for the MOC for the main thread and then hangs when doing one of the MOCs for the background threads.
Thread 2 starts to save but never acquires the lock for saving, because the other thread is stuck trying to merge changes.
So, why is it hanging when merging changes? Is there a better way to handle this scenario?
Update: I have tried using [persistentStoreCoordinator lock] around my [MOC save] call instead of just a lock with NSLock, to no avail. I also tried adding [NSThread sleepForTimeInterval:2] before the call to [self saveContext] in one of the dispatch_group_async calls, and it didn't help.
Update 2: Perhaps the better question here is why I was getting merge conflicts (Cocoa Error 133020). Is that expected? Am I doing the merge right (merging to all contexts except the one saving)?
Update 3: I've posted another question to address the larger context of how I'm doing the multithreading.
When you're creating an NSManagedObject subclass, you're inserted it into a context and therefore you can only do that on the queue / thread that belongs to the context.
The same goes for calling -save on the context.
It seems to me from your code that you're inserting two objects — each on their own thread. That will not work.
I've posted this question documenting my situation a little better. This current question I think is a little narrow in scope.

How do I create a child NSManagedObjectContext?

I've seen a few videos / threads that say it's possible to create 'children' MOCs -- MOCs that use other MOCs as their persistant stores. Useful, for example, in a context where you're threading your application, and want to have a single master MOC that can save / rollback the changes that the child threads create. (From what I understand, a MOC and it's managedObjects MUST all be used on the same thread)
The question is, how do I create a child MOC? I can't track down the WWDC videos I was watching that introduced them, and everything I"ve seen has been talking about how to use them ONCE they're made. I can easily alloc a new MOC, but how do I set it's persistent store to be another MOC? The reference doesn't show any functions that do that!
Create a new MOC for which you are in total control of the synchronization. This is the same as calling init and the same behavior as pre-parent/child relationships.
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSConfinementConcurrencyType];
You parent a MOC to another MOC by setting its property:
moc.parentContext = someOtherMocThatIsNowMyParent;
Here, the child chooses the parent. I'm sure my kids wish they were NSManagedObjectContexts. A parent context must be of either NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType.
You can create a MOC that is "bound" to a private queue:
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
which means you should only access it via the performBlock or performBlockAndWait API. You can call those methods from any thread as they will ensure proper serialization of the code in the block. For example...
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = someOtherMocThatIsNowMyParent;
[moc performBlock:^{
// All code running in this block will be automatically serialized
// with respect to all other performBlock or performBlockAndWait
// calls for this same MOC.
// Access "moc" to your heart's content inside these blocks of code.
}];
The difference between performBlock and performBlockAndWait is that performBlock will create a block of code, and schedule it with Grand Central Dispatch to be executed asynchronously at some time in the future, on some unknown thread. The method call will return immediately.
performBlockAndWait will do some magic synchronization with all the other performBlock calls, and when all the blocks that have been presented prior to this one are done, this block will execute. The calling thread will pend until this call has completed.
You can also create a MOC that is "bound" to the main thread, just like private concurrency.
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSMainQueueConcurrencyType];
moc.parentContext = someOtherMocThatIsNowMyParent;
[moc performBlock:^{
// All code running in this block will be automatically serialized
// with respect to all other performBlock or performBlockAndWait
// calls for this same MOC. Furthermore, it will be serialized with
// respect to the main thread as well, so this code will run in the
// main thread -- which is important if you want to do UI work or other
// stuff that requires the main thread.
}];
which means you should only access it directly if you know you are on the main thread, or via the performBlock or performBlockAndWait API.
Now, you can use the "main concurrency" MOC either via the performBlock methods, or directly if you know you are already running in the main thread.
Initialize child MOC then:
[_childMOC performBlockAndWait:^{
[_childMOC setParentContext:parentMOC];
}];

Strange behavior when using child/parent NSManagedObjectContext

I'm developing an application where I need to both calculate things (multiple seconds operations) and write things (sync data with a server) on a background thread.
Because of this I use two NSManagedObjectContexts (MOC), a child and a parent, and these must always be in sync. To make sure they are in sync I always edit/add data to the child MOC so it gets pushed to the main MOC with the following pattern:
[childMOC performBlock:^{
MyObject *myObject = *create new object in childMOC*
[childMOC save:&error];
[mainMOC performBlock:^{
[mainMOC save:&error];
// Is this mandatory to make it work correctly?
// [childMOC performBlock:^{
// [childMOC refreshObject:myObject mergeChanges:NO];
// }];
}];
}];
After a while I seemed to have two versions of the same object in the background context, one with a temporary ID and one with a permanent ID. If I e.g. added child objects to the "real" one (by passing fresh permanent ID from main to child MOC) I didn't see these objects when I retrieved my object in the background MOC cause it is the old temporary one that is cached.
I've seen the pattern above been used a lot, but it seems strange that no one mentioned this temporary/permanent ID problem.
It doesn't feel right that it can be two versions of the same object within a context. If I pass an NSManagedObjectID to the child MOC and retrieve that, shouldn't the child MOC update my existing object instead of creating a new one and leave my old temporary as cached default?
Do I need to use the commented row on each place I create an object?
Or maybe it works with mergeChangesFromContextDidSaveNotification, will that give the same effect?
My solution was to:
1) Use the background MOC as the parent MOC and the main MOC as a child. As a bonus I don't need to save the main MOC to get the permanent IDs.
[DC.backgroundMOC performBlock:^{
// Add, save and update managed objects
[DC saveContext]; // The changes is being pushed to the main context
}];
2) Use NSManagedObjectContextDidSaveNotification to keep the main MOC up to date (the main MOC is updating the UI)
- (void) backgroundMOCSaved:(NSNotification*)notification {
[mainMOC performBlock:^{
[mainMOC mergeChangesFromContextDidSaveNotification:notification];
}];
}
I had this problem and the solution was making sure all operations on the parent MOC are done with performBlock:, even the initial setup:
parentManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[parentManagedObjectContext performBlock:^{
[parentManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[parentManagedObjectContext setPersistentStoreCoordinator:coordinator];
}];
Once I did this, my child MOCs started picking up changes.