Core Data relationships, multiple managed object contexts & threads - ios

This is a bit of a tricky one.
I have Document entities that are currently being imported into CoreData from a SQLite database on a background thread. There's a separate context for the background thread and I am batching the save at every 500 entries.
Saving the background thread context triggers a notification which grabs my main thread's contexts and performs a merge between the two.
Everything works as expected if I am only importing document entities.
My problem occurs when I try and establish a relationship between the current document being created, an another entity called briefcase.
This is what my import routine currently does:
Create Briefcase entity
Loops through SQLite database rows and creates Document entities for each row
Create the relationship between the document in the loop and the briefcase entity
At every 500 rows, I save & reset the context. This triggers a ContextSave notification which grabs the main thread and merges with the main thread's context.
This is where my issue is: after the save & reset above, my Briefcase entity gets merged with the main thread so when my loop continues, the next document entity created tries to associate itself with the briefcase, which is where I get a crash saying I can't establish relationships between objects on separate threads.
I know that if I remove the call to reset the context after saving it, everything works as expected but my memory footprint goes way up and it is not something I am prepared to accept.
So my question is:
Can you think of a way of keeping the Briefcase entity around (and valid) for the entire import process so I can continue to create the relationships?
My first thought was to create the briefcase entity without a context and then add it to the context once the whole process is finished. This didn't work very well (it crashed on creation).
Your thoughts are very much appreciated.
Rog

Answering my own question:
Create Briefcase entity
Loop through SQLite database rows and create Document entities for each row
Create the relationship between the document in the loop and the briefcase entity
At every 500 rows, save context & immediately store the objectID for Briefcase after saving
Now it's ok to reset the context
(Re)retrieve the briefcase instance using the objectID saved above and the existingObjectWithID:error: method
Loop continues...

Related

What happens to the other NSManagedObjects on a context when context.reset() is called?

I have a scenario where an Entity has many relationships with other entities. I did some changes in the NSManagedObject of the entity and discarded those changes.
Right now I'm calling managedObjectContext.refresh(entity, mergeChanges: false) and then managedObjectContext.refresh(relatedEntity, mergeChanges: false) on every related entity to ensure having no dangling objects in the context.
What would be the difference if I directly call managedObjectContext.reset()? Should I still need to refresh or mark nil the related entities?
Is there any way to make this code more optimized?
If you call reset, you need to also immediately stop using every managed object you have fetched from that context. All fetches need to be redone after the reset, because it makes the context forget about everything it has already fetched.
There are various patterns for more efficiently creating discardable changes like you describe. One popular choice is:
Create a new managed object context and make it a child context of the current context.
Make your changes in that context.
If you want to save changes, save them. If not, just don't bother. The child context will be deallocated and its changes will be lost.

CoreData: saving data to context

I am working on to use CoreData to save data of an object to an entity created in the datamodeld file.
In my scenario, I made a datamodeld called Product which represents an item available in the grocery store.
I have several categories for each type of products. For example, produce, dairy, meat, etc... If I save those products in the Product context, they will all be aggregated into one database and retrieving the data will be a hassle.
I'm wondering if it is possible to store my items into different databases of the Product context.
The only workaround I can think of is to create an entity for each categories which is a child of Product. I don't like this workaround because I am creating unnecessarily entities (every products in each categories have the same attributes).
By "product context", I guess you mean managedObjextContext? Your data is not saved in moc, you write data on moc and it's reflected persistent store.
You can use multiple moc to save the data of your app but be extra careful to syncronize the moc to moc on main thread.
I did something on the same lines that you are proposing as a work around. It worked out fine in my case.

Core Data multiple context uniqueness

I am using the core data stack shown in the image below. I want to design a structure where objects can be created in both worker contexts.
What I am observing in the setup is if both contexts try to create the same object (for a unique key) at around the same time, db ends up in creating two rows for the table. Is there a way to solve this? Thanks in advance for your response.
The only way you can ensure uniqueness would be to have a coordinating object that all contexts turn to to verify their operation (a "uniqueness enforcer" if you will).
The general algorithm is described HERE, however you fall under the "multi-threaded/context" category and this will complicate things.
In a multi-threaded environment, your enforcer would have to perform a save to the store (using its own managed object context) before returning results to the calling object.
The general flow would be (no cache version):
A context request object for keys from the enforcer
The enforcer issue the request "under lock" (either locking an actual lock or using a serial dispatch queue)
the enforcer query the store for existing objects
create objects for missing keys and save them
you might want to mark the objects as stubs, as the caller might not eventually save and it will give you a flag to ignore them in your fetch requests in your views
build the results array with the objects he created
the results might be NSManagedObjectIDs or imported objects in the caller context otherwise you risk cross context access of managed objects

Core data and data integrity: read operations vs write operations. How to protect?

I have what I think is a simple task.
I have a method called [self getPerson] that makes a simple GET request from a web service for a Person that returns some JSON and then transforms the JSON into an NSManagedObject. checks for an existing identical Person NSManagedObject, and if none is found, saves the Person into core data. No problem.
However, If I fire off this method twice in a row, I get two Person NSMangedObjects persisted into Core Data. For example:
[self getPerson];
[self getPerson]; ---> yields duplicate `Person` objects saved in core data, no good.
How can I ensure that only one Person object is saved in Core Data (no duplicates allowed)?
I know the issue, I just don't know how to fix it. The issue is that I need a transaction. When [self getPerson] fires the first time, the method checks for an already existing identical Person object, finds none, and saves a new Person into core data. This is correct. When I fire [self getPerson] the second time, the method checks for an already existing Person object, doesn't see one, and is then persisting another Person object. This is not correct. I would like to make it so that the second time, and third time, and fourth time, to the 1000th time, checking for an existing Person object will only occur after the managedObjectContext saveoperation is done. Right now the check for an existing object is happening so fast (before the save is done).
Do I need a serial queue? If so, should this be a dispatch_async or dispatch_sync? I've even toyed with the idea of trying to use a performSelectorWithDelay trick.
Once you create the object it will exist in the database regardless of you calling save. So you should not create a managed object if one exists already. It's not entirely clear what your code logic is but from your description you say you transform the JSON to a managed object and then you check for an identical existing one and if none is found you save. Well when you create the managed object you have created it, so it's too late to check if an identical one exists. Saving does not create the object it just saves it to the store if it hasn't already been saved.
So first check if an person object exists with the attributes in the JSON and if not then create a managed object.
Well, in this case a serial queue will ensure that operations are performed in the correct manner.
From you question, maybe I'm missing something, I cannot understand if the getPerson method is responsible to both get and save data. If not, you should do it.
Anyway, if you use JSON and the person you retrieve form the server has a unique identifier, you should use that to query against Core Data and verify if it exists or not. The correct manner to do it is to implement Implementing Find-or-Create Efficiently.
A simple question. Is the any reason for calling the getPerson twice? Could you not prevent it using a flag (or a transient property)? Just simple ideas.

Merging two managedObjectContexts

I have two managedObjectContexts(MOC): a temporaryMOC and a persistedMOC. If I initialize an entity in the tempMOC and the user decides to save, I save and merge the changes into the persistedMOC and save. In which MOC are now those entities? If the user start a new file, there will be now two entities in the tempMOC?
The MOCs is only a scratchpad with which you can create, update, delete and retrieve NSManagedObjects.
If you've got two objects as a result of a save operation - it means you've mistakenly created two entities that are saved with the merge.
Could you show us the code in which you create an object?

Resources