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
Related
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).
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.
I've recently been learning about Core Data and specifically how to do inserts with a large number of objects. After learning how to do this and solving a memory leak problem that I met, I wrote the Q&A Memory leak with large Core Data batch insert in Swift.
After changing NSManagedObjectContext from a class property to a local variable and also saving inserts in batches rather than one at a time, it worked a lot better. The memory problem cleared up and the speed improved.
The code I posted in my answer was
let batchSize = 1000
// do some sort of loop for each batch of data to insert
while (thereAreStillMoreObjectsToAdd) {
// get the Managed Object Context
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
managedObjectContext.undoManager = nil // if you don't need to undo anything
// get the next 1000 or so data items that you want to insert
let array = nextBatch(batchSize) // your own implementation
// insert everything in this batch
for item in array {
// parse the array item or do whatever you need to get the entity attributes for the new object you are going to insert
// ...
// insert the new object
let newObject = NSEntityDescription.insertNewObjectForEntityForName("MyEntity", inManagedObjectContext: managedObjectContext) as! MyManagedObject
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}
// save the context
do {
try managedObjectContext.save()
} catch {
print(error)
}
}
This method seems to be working well for me. The reason I am asking a question here, though, is two people (who know a lot more about iOS than I do) made comments that I don't understand.
#Mundi said:
It seems in your code you are using the same managed object context,
not a new one.
#MartinR also said:
... the "usual" implementation is a lazy property which creates the
context once for the lifetime of the app. In that case you are reusing
the same context as Mundi said.
Now I don't understand. Are they saying I am using the same managed object context or I should use the same managed object context? If I am using the same one, how is it that I create a new one on each while loop? Or if I should be using just one global context, how do I do it without causing memory leaks?
Previously, I had declared the context in my View Controller, initialized it in viewDidLoad, passed it as a parameter to my utility class doing the inserts, and just used it for everything. After discovering the big memory leak is when I started just creating the context locally.
One of the other reasons I started creating the contexts locally is because the documentation said:
First, you should typically create a separate managed object context
for the import, and set its undo manager to nil. (Contexts are not
particularly expensive to create, so if you cache your persistent
store coordinator you can use different contexts for different working
sets or distinct operations.)
What is the standard way to use NSManagedObjectContext?
Now I don't understand. Are they saying I am using the same managed
object context or I should use the same managed object context? If I
am using the same one, how is it that I create a new one on each while
loop? Or if I should be using just one global context, how do I do it
without causing memory leaks?
Let's look at the first part of your code...
while (thereAreStillMoreObjectsToAdd) {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
managedObjectContext.undoManager = nil
Now, since it appears you are keeping your MOC in the App Delegate, it's likely that you are using the template-generated Core Data access code. Even if you are not, it is highly unlikely that your managedObjectContext access method is returning a new MOC each time it is called.
Your managedObjectContext variable is merely a reference to the MOC that is living in the App Delegate. Thus, each time through the loop, you are merely making a copy of the reference. The object being referenced is the exact same object each time through the loop.
Thus, I think they are saying that you are not using separate contexts, and I think they are right. Instead, you are using a new reference to the same context each time through the loop.
Now, your next set of questions have to do with performance. Your other post references some good content. Go back and look at it again.
What they are saying is that if you want to do a big import, you should create a separate context, specifically for the import (Objective C since I have not yet made time to learn Swift).
NSManagedObjectContext moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
You would then attach that MOC to the Persistent Store Coordinator. Using performBlock you would then, in a separate thread, import your objects.
The batching concept is correct. You should keep that. However, you should wrap each batch in an auto release pool. I know you can do it in swift... I'm just not sure if this is the exact syntax, but I think it's close...
autoreleasepool {
for item in array {
let newObject = NSEntityDescription.insertNewObjectForEntityForName ...
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}
}
In pseudo-code, it would all look something like this...
moc = createNewMOCWithPrivateQueueConcurrencyAndAttachDirectlyToPSC()
moc.performBlock {
while(true) {
autoreleasepool {
objects = getNextBatchOfObjects()
if (!objects) { break }
foreach (obj : objects) {
insertObjectIntoMoc(obj, moc)
}
}
moc.save()
moc.reset()
}
}
If someone wants to turn that pseudo-code into swift, it's fine by me.
The autorelease pool ensures that any objects autoreleased as a result of creating your new objects are released at the end of each batch. Once the objects are released, the MOC should have the only reference to objects in the MOC, and once the save happens, the MOC should be empty.
The trick is to make sure that all object created as part of the batch (including those representing the imported data and the managed objects themselves) are all created inside the autorelease pool.
If you do other stuff, like fetching to check for duplicates, or have complex relationships, then it is possible that the MOC may not be entirely empty.
Thus, you may want to add the swift equivalent of [moc reset] after the save to ensure that the MOC is indeed empty.
This is a supplemental answer to #JodyHagins' answer. I am providing a Swift implementation of the pseudocode that was provided there.
let managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = (UIApplication.sharedApplication().delegate as! AppDelegate).persistentStoreCoordinator // or wherever your coordinator is
managedObjectContext.performBlock { // runs asynchronously
while(true) { // loop through each batch of inserts
autoreleasepool {
let array: Array<MyManagedObject>? = getNextBatchOfObjects()
if array == nil { break }
for item in array! {
let newEntityObject = NSEntityDescription.insertNewObjectForEntityForName("MyEntity", inManagedObjectContext: managedObjectContext) as! MyManagedObject
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}
}
// only save once per batch insert
do {
try managedObjectContext.save()
} catch {
print(error)
}
managedObjectContext.reset()
}
}
These are some more resources that helped me to further understand how the Core Data stack works:
Core Data Stack in Swift – Demystified
My Core Data Stack
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.
There is a core data model with two entities i my iOS application.
Entity: Unit / Attributes: unit_name - NSString
->> Relationship to Exercise (one to many)
Entity: Exercise / Attributes: exercise_name - NSString .
So one unit can have many exercises.
In my table view controller are all available exercises listed.
(So in the first time, i make a fetch request for the Exercise entity and the managedObjectContext points to this entity.
If i want to save a "NEW" unit with exercises the save function doesn't work.
There is no error at all, but the unit table is still empty.
Here is the code for the save function:
Units *newUnit = [NSEntityDescription insertNewObjectForEntityForName:#"Units" inManagedObjectContext:[self.coreDataHelper managedObjectContext]];
newUnit.unit_name = unitTextField.text;//Attribute
newUnit.exercises = exerciseSet;//Relationship (NSSet)
NSError *error = nil;
if (![[self.coreDataHelper managedObjectContext]save:&error]) {
NSLog(#"There was an error! %#", error);
}
else {
NSLog(#"Success!");
}
It seems like the managedObjectContext still points to the Exercise entity. (Because it was initialized the first time with this entity) the coreDataHelper has the NSPersistentStoreCoordinator, the NSManagedObjectContext, the NSManagedObjectModel and some methods to read write and delete.
Thanks for help!
Just to verify that everything is connected the way it ought to be, add
NSAssert(self.coreDataHelper, #"null coreDataHelper");
NSAssert(self.coreDataHelper.managedObjectContext, #"null MOC");
NSLog(#"available entities: %#",
self.coreDataHelper.managedObjectContext.persistentStoreCoordinator.managedObjectModel.entitiesByName);
You ought to see "Units" as one of your entities, if you've set everything up correctly.
And then after your insertion of newUnit, verify that it worked:
NSAssert(newUnit, #"newUnit didn't get inserted");
This smells like a logic error to me, by the way: you're creating a new Units instance every time you save? Are you sure you don't want to use a find-or-create pattern instead?