CoreData Multiple NSManagedObjectContext Save Notification Clarification - ios

i just wanted to make sure what am doing is right!
I have used Parent-Child NSManagedObjectContext pattern, where
i have one default private queue NSManagedObjectContext with type NSPrivateQueueConcurrencyType, and
i have one MainQueue NSManagedObjectContext with type NSMainQueueConcurrencyType , whose parent is the default private queue ,
and for each view controller i will create one private queue context with parent of main queue context,
it goes like this,
private context ->
main context ->
other context
so my question is, does this setup requires NSManagedObjectContextDidSaveNotification to propagate changes to parent context or it will be automatically bubbled up since all other context is child of parent and main context
because currently am using context save notification to merge changes and am getting following errors lot of times
Fatal Exception: NSInternalInconsistencyException
This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.
2 CoreData 0x2f2f44c9 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 3228
3 CoreData 0x2f315db1 -[NSManagedObjectContext save:] + 824
4 App 0x000a3279 -[CoreDataManager saveContext:withCompletionBlock:] (CoreDataManager.m:144)
5 App 0x000a31f9 __46-[CoreDataManager contextDidSaveNotification:]_block_invoke (CoreDataManager.m:134)
6 CoreData 0x2f3798f9 developerSubmittedBlockToNSManagedObjectContextPerform_privateasync + 68

It seems like you are merging the notification to another child context. How I would typically do this is,
MainViewController
Main Context with persistent store coordinator
Does not do any thing else than managing the child context.
ViewController 1
Has a child managed object context, whose parent is main context
You would save this and push the changes to the main context like so,
[chilContext performBlock:^{
[childContext save:&error];
if(!error){
[childContext.parentContext performBlock:^{
[childContext.parentContext save:&error];
}];
}
}];
ViewController 2
Would you watching NSManagedObjectContextDidSaveNotification from parent object and if it finds the context being saved, it refreshes the objects by merging the changes from this notification.
- (void)parentContextSaved:(NSNotification*)note{
if(![NSThread isMainThread]){
[self performSelector:#selector(parentContextSaved:) onThread:[NSThread mainThread] withObject:note waitUntilDone:NO];
}
[childObjectContext performBlock:^{
[childObjectContext mergeChangesFromContextDidSaveNotification:note];
}];
}
I use this approach and this works well.

Related

CoreData fetch from privateContext nil while record exists on MainContext

I've two CoreData contexts: one private for background processes and the MainContext associated with the UI thread. They are independent each other and are directly backed by the same NSPersistenceStoreCoordinator. If I perform a certain fetch from the MainContext my record returns an object, if I do the same on the privateContext I get nil.
Do you have any idea of what's happening?
Thank you
UPDATE (to answer to The Dreams Wind)
A piece of code to see how I manage the data:
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
privateContext.persistentStoreCoordinator = appDelegate.persistentContainer.persistentStoreCoordinator
privateContext.perform {
//I create some records like this
var myObj = MyObject(context: privateContext)
//assign properties
privateContext.save()
}
The save() triggers my CoreData didSave callback in the AppDelegate where I merge the two context to keep the main context updated
let senderMoc = notification.object as! NSManagedObjectContext
if senderMOC != mainContext{
mainContext.perform {
mainContext.mergeChanges(fromContextDidSave: notification as Notification)
}
}
I expected that the mainContext could be behind the private one due to pending objects not saved yet, not the contrary.
The private context is instantiated inside a method, so I think it doesn't retain any object. Is it right? The main context, on the contrary lives as long as the app is running, so this should retain all objects it already have loaded in memory from NSPersistenceStoreCoordinator right?

iOS: Synchronizing access to CoreData

I'm new to CoreData and I'm trying to create a simple application.
Assume I have a function:
func saveEntry(entry: Entry) {
let moc = NSManagedObjectContext(concurrencyType: .NSPrivateQueueConcurrencyType)
moc.parentContext = savingContext
moc.pefrormBlockAndWait {
// find if MOC has entry
// if not => create
// else => update
// saving logic here
}
}
It can introduce a problem: if I call saveEntry from two threads, passing the same entry it will duplicate it. So I've added serial queue to my DB adapter and doing in following manner:
func saveEntry(entry: Entry) {
dispatch_sync(serialDbQueue) { // (1)
let moc = NSManagedObjectContext(concurrencyType: .NSPrivateQueueConcurrencyType)
moc.parentContext = savingContext
moc.pefrormBlockAndWait { // (2)
// find if MOC has entry
// if not => create
// else => update
// saving logic here
}
}
}
And it works fine, until I'd like to add another interface function:
func saveEntries(entries: [Entry]) {
dispatch_sync(serialDbQueue) { // (3)
let moc = NSManagedObjectContext(concurrencyType: .NSPrivateQueueConcurrencyType)
moc.parentContext = savingContext
moc.pefrormBlockAndWait {
entries.forEach { saveEntry($0) }
}
}
}
And now I have deadlock: 1 will be called on serialDbQueue and wait till saving finishes. 2 will be called on private queue and will wait for 3. And 3 is waiting for 1.
So what is correct way to handle synchronizing access? As far as I understand it's not safe to keep one MOC and perform saves on it because of reasons described here: http://saulmora.com/coredata/magicalrecord/2013/09/15/why-contextforcurrentthread-doesn-t-work-in-magicalrecord.html
I would try to implement this with a single NSManagedObjectContext as the control mechanism. Each context maintains a serial operation queue so multiple threads can call performBlock: or performBlockAndWait: without any danger of concurrent access (though you must be cautious of the context's data changing between the time the block is enqueued and when it eventually executes). As long as all work within the context is being done on the correct queue (via performBlock) there's no inherent danger in enqueuing work from multiple threads.
There are of course some complications to consider and I can't offer real suggestions without knowing much more about your app.
What object will be responsible for creating this context and how will it be made available to every object which needs it?
With a shared context it becomes difficult to know when work on that context is "finished" (it's operation queue is empty) if that represents a meaningful state in your app.
With a shared context it is more difficult to abandon changes should you you want to discard unsaved modifications in the event of an error (you'll need to actually revert those changes rather than simply discard the context without saving).

Passing a managed object from private context to main context: it looses its related objects

I have an NSManagedObject in a private context I have associated to a private queue. This NSManagedObject has a relationship to many other NSManagedObjects. I'd like to pass this former NSManagedObject with all its "nested" related objects to my main context without saving the private context, so I'm trying to do this:
__block NSManagedObjectID *privateObjectID = nil;
[privateContext performBlockAndWait: ^{
MyEntity *privateMyEntity = [self createMyEntityInContext:privateContext];
privateObjectID = [privateMyEntity valueForKeyPath:#"objectID"];
}];
if (privateObjectID != nil) {
self.currentMyEntity = [mainContext objectWithID:privateObjectID];
}
But I find that the self.currentMyEntity has lost its related objects (its NSSet is empty).
However, if I save the private context before passing the managed object, then in self.currentMyEntity I get also the related objects:
__block NSManagedObjectID *privateObjectID = nil;
[privateContext performBlockAndWait: ^{
MyEntity *privateMyEntity = [self createMyEntityInContext:privateContext];
[self saveContext:privateContext];
privateObjectID = [privateMyEntity valueForKeyPath:#"objectID"];
}];
if (privateObjectID != nil) {
self.currentMyEntity = [mainContext objectWithID:privateObjectID];
}
Why? I don't want to save the object in the private context...
Thanks in advance
I think you should read the Core Data Programming Guide, and then you should reevaluate what you are trying to accomplish.
First, if you don't save the data anywhere, how do you expect a separate context to fetch anything about the object? As far as that context is concerned, the object does not even exist? What do they have in common? The persistent store coordinator? The persistent store? If the entities they have in common don't have the data how are the different contexts supposed to exchange the data?
Second, you can't pass temporary object IDs between contexts and expect to use them.
Third, objectWithID always returns an object, even if that object does not exist, so you should only use it in certain situations.
If you don't want to save the object in the private context, then what do you want to do with it?
If you just want to pass it to the other context, then you can create the private context as a child context of the other context. When you save from the private context, it will simply save the data up into that context, without saving to the database.

Multi Context CoreData with Threads

UPDATE : I suppose the issue is that the parent context is not updated when the child context is saved. Still, need help.
I have tried many examples of Multi-context (Parent-Child) Core Data.
Previously my app was using the traditional way of storing data, i.e. I used an OperationQueue where I fetch the data from the server and save to the DB using a MOC and on saving fire a notification to the mainMOC to mergeChanges : NSManagedObjectContextDidSaveNotification.
Without disturbing the flow of the app, (i.e. removing the OperationQueue), I tried to implement the Parent-Child ManagedObjectContext Relationship where I used a privateMOC with concurrencyType as NSPrivateQueueConcurrencyType which has a persistantStoreCoordinator, and the mainMOC with concurrenyType as NSMainQueueConcurrencyType which is a child of the privateMOC. And in the Queue I have a tempMOC with concurrencyType as NSPrivateQueueConcurrencyType which is a child of the mainMOC.
While saving, I nest the performBlock of the three MOCs as -
[tempMOC performBlock:^{
if (![tempMOC save:&error]) {
NSLog(#"Error : %#",error);
}
[mainMOC performBlock:^{
if (![mainMOC save:&error]) {
NSLog(#"Error : %#",error);
}
[privateMOC performBlock:^{
if (![privateMOC save:&error]) {
NSLog(#"Error : %#",error);
}
}];
}];
}];
I am getting Errors like CoreData 1560 and 1570 while the mainMOC is trying to save. NSValidationErrorKeyerror where it says some value is nil.
Is it that the changes of the tempMOC are not going to the mainMOC ? I did not dig in but as far as I know, it should not be nil.
What could possibly be the error? Please Help.
UPDATE : I tried to print the objects of tempMOC and I see proper values like :
<Element_Name: 0xc0b59c0> (entity: Element_Name; id: 0xc07ca90 <x-coredata:///Element_Name/t2DCD57A8-4C1A-4AF7-A10E-5B9603E2BB8730> ; data: {
tag1 = nil;
tag2 = 3430065;
tag3 = 600;
tag4 = N;
tag5 = "2013-10-29 00:00:00 +0000";
tag6 = nil;
tag7 = 327842701;
relation = "0xbf1f760 <x-coredata://87C54A94-701E-4108-826E-4D98A53380F9/Relation/p1>";
tag8 = "Some_Value";
I tried to print the objects of mainMOC and I see nil value instead of the data like :
<Element_Name: 0xbd47a50> (entity: Element_name; id: 0xc0b14b0 <x-coredata:///Element_Name/t2DCD57A8-4C1A-4AF7-A10E-5B9603E2BB8740> ; data: {
tag1 = nil;
tag2 = nil;
tag3 = 0;
tag4 = nil;
tag5 = nil;
tag6 = nil;
tag7 = nil;
relation = "0xbd586c0 <x-coredata://87C54A94-701E-4108-826E-4D98A53380F9/relation/p1>";
tag8 = nil;
I just hit the same problem and found a solution. Without the rest of your code I cannot assure this would solve your problem, but it did solve mine.
I was instantiating some NSManagedObject classes, modifying some of their properties and THEN inserting them in the temp or child NSManagedObjectContext. All the properties were showing just fine like in your case.
But when I saved that context and the changes got pushed to the parent NSManagedObjectContext, all properties were nullified (like in your case).
I have not observersed this behaviour when using only one NSManagedObjectContext, and I have not experimented with the older NSManagedObjectContextDidSaveNotification pattern.
The solution is of course to add the NSManagedObject to a context just after initialisation, and before any property assignments are done.
vshall,
If you already have a background insertion MOC pattern working, why are you trying to move to a parent-child MOC situation? It isn't faster. And, judging from what I can see about your implementation, you end up blocking the main thread.
There are many good reasons to use the parent-child MOC relationship. Most of them involve creating scratch or read-only MOCs. The second big use case is to have your main MOC be a child of a private concurrent MOC. That way saves are "fast" and done on the background thread. Background insertion into a child concurrent MOC of the main MOC, in my experience, is slower and causes the UI to stutter.
In answer to your question, you are trying to access items before your embedded set of saves has finished. Hence, your data is corrupt and you get exceptions.
Andrew

Core Data and Managed Object Contexts

I'm new with Core Data and I have some questions about how to do the things in the right way. I think I need a temporaryManagedObjectContext to work with temporary entities, while user is editing a new entity. When user tap save that data I want to insert those entities in the persistedManagecObjectContext and then saveContext. What is the best practice to achieve this? Since I don't want to save in the temp context, it is mandatory to use threading?
Thanks for your knowledge!
You need to merge the changes in the temporary ManagedObjectContext (MOC) into the persisted one. This is an example of how I achieved this.
Im using threading (One MOC per thread), but Im pretty sure it should work out fine without threads aswell.
You call this method to save your changes in the tempMOC:
- (void) saveNewEntry {
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
// Subscribe to "NSManagedObjectContextDidSaveNotification"
// ..which is sent when "[tempMOC save];" is called.
[dnc addObserver: self
selector: #selector(mergeChanges:)
name: NSManagedObjectContextDidSaveNotification
object: tempMOC];
// Save changes
[tempMOC save];
// Remove subscribtion
[dnc removeObserver: self
name: NSManagedObjectContextDidSaveNotification
object: tempMOC];
}
...which will fire off a notification to:
- (void) mergeChanges: (NSNotification*) saveNotification {
// If youre using threads, this is necessary:
[self performSelectorOnMainThread: #selector(mergeToMainContext:)
withObject: saveNotification
waitUntilDone: NO];
// ...otherwise, you could do the merge in this method
// [persistedMOC mergeChangesFromContextDidSaveNotification: saveNotification];
}
...which in turn calls:
- (void) mergeToMainContext: (NSNotification*) saveNotification {
[persistedMOC mergeChangesFromContextDidSaveNotification: saveNotification];
}
I have the same issue (editor needs an object, so create... but if cancel, throw away that object). I ended up with a more brute-force approach, though, precisely to avoid all the multiple objet contexts and merging:
I use a "is temp" field in all my objects. When I need to create a "provisional" object I have a method in my data layer that creates an object as normal, but then flips isTemp=true before returning.
Then, in my "object editor" onCancel there is:
if (obj.isTemp) {
[context deleteObject: ... ];
}
onSave is:
if (obj.isTemp) obj.isTemp = NO;
[context saveAsUsual...];
Note: not addressed here there's the issue of not copying "throw away" changes into an existing object until user confirms saving. Otherwise changes would be sitting there like a trojan horse waiting to be applied when some other code saves the shared context.

Resources