How does Core data concurrency work with multithreading in Swift 3? - ios

In my program, it uses both of
DispatchQueue.global(qos: .background)
and
self.concurrentQueue.sync(flags: .barrier)
to deal with the background multithread issues.
It is swift 3 so I use the latest way to get the childContext:
lazy var context: NSManagedObjectContext = {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.newBackgroundContext()
}()
I also enable -com.apple.CoreData.ConcurrencyDebug 1 to debug
Then the problem occurs:
1, When there's an API call and in the callback block (background thread), I need to fetch the core data, edit, then save. I tried to use self.context from the code above to call performBlockAndWait and do save inside of this block. The whole process goes fine but when I try to access my result outside of this block but inside of the callback block, the error occurs. I have also tried to get the objectId and getObjectById by both self.context and self.context.parent and the error occurs on this line. What did I do wrong and how should I do this? since I need to use the result everywhere in many different thread (not context).
2, I read a post says that I need one context per thread, then in my case, how do I determine which exact thread it is if it's a call back from API call and do I really need to do this?
3, You might ask that why do I need a privateConcurrentType, because my program has things need to be running in background thread so that it has to do it this way, (read from other post), is this right?
4, Even in my question 1, get object by passing objectId to different Context still not working in my case. Let's assume this is the proper way. How am I gonna manage passing so many objectID throughout my entire program in different thread without being super messy? To me this sounds crazy but I suppose there's a much cleaner and easier way to deal with this.
5, I have read many posts some are pretty old (before swift 3), they have to do childContext.save then parentContext.save, but since I use the code above (swift 3 only). It seems that I can do childContext.save only to make it work? Am I right?

Core data in general is not multithreading friendly. To use it on concurrent thread I can assume only bad things will happen. You may not simply manipulate managed objects outside the thread on which the context is.
As you already mentioned you need a separate context per thread which will work in most cases but by my experience you only need one background context which is read-write and a single main thread read-only context that is used for fetch result controllers or other instant fetches.
Think of a context as some in-memory module that communicates with the database (a file). Fetched entities are shared within the context but are not shared between contexts. So you can modify pretty much anything inside the context but that will not show in the database or other contexts until you save the context into the database. And if you modify the same entity on 2 contexts and then save them you will get a conflict which should be resolved by you.
All of these then make quite a mess in the code logic and so multiple contexts seem like something to avoid. What I do is create a background context and then do all of the operations on that context. Context has a method perform which will execute the code on its own thread which is not main (for background context) and this thread is serial.
So for instance when doing a smart client I will get a response from server with new entries. These are parsed on the fly and I perform a block on context to get all the corresponding objects in the database and create the ones that do not exist. Then copy the data and save the context into database.
For the UI part I do similar. Once an entry should be saved I either create or update the entity on the background context thread. Then usually do some UI stuff on completion so I have a method:
public func performBlockOnBackgroundContextAndReturnOnMain(block: #escaping (() -> Void), main: #escaping (() -> Void)) {
if let context = context {
context.perform {
block()
DispatchQueue.main.async(execute: { () -> Void in
main()
})
}
}
}
So pretty much all of the core data logic happens on a single thread which is in background. For some cases I do use a main context to get items from fetch result controller for instance; I display a list of objects with it and once user selects one of the items I refetch that item from the background context and use that one in the user interface and to modify it.
But even that may give you trouble as some properties may be loaded lazily from database so you must ensure that all the data you need will be loaded on the context and you may access them on the main thread. There is method for that but I rather use wrappers:
I have a single superclass for all the entities in the database model which include id only. So I also have a superclass wrapper which has all the logic to work with the rest of wrappers. What I am left with in the end is that for each of the subclass I need to override 2 mapping methods (from and to) managed object.
It might seem silly to create additional wrappers and to copy the data into memory from managed object but the thing is you need to do that for most of the managed objects anyway; Converting NSData to/from UIImage, NSDate to/from Date, enumerations to/from integers or strings... So in the end you are more or less just left with strings that are copied 1-to-1. Also this makes it easy to have the code that maps the response from your server in this class or any additional logic where you will have no naming conflicts with managed objects.

Related

CoreData - usage of backgroundContext

I am trying to understand a concept of backgroundContext in CoreData. Though I read several articles about it, I am not still sure about its purpose.
I have an app using CoreData that allows user create, update or delete records. User can also fetch the data he added. I want to ensure that if there are a lot of records, it will not influence a flow of the UI while fetching data.
So I studied a bit about backgroundContexts. I implemented following according to what I understood. I am not sure though whether it is a correct solution. My idea is - if I fetch in background, it cannot influence the main thread.
//I have an PersistentContainer created by Xcode.
//I create an backgroundContext.
self.backContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.newBackgroundContext()
//Then if the user adds a new record, it's added to backContext and saved to mainContext
...
let newRecord = Record(context: self.backContext!)
...
self.backContext!.save()
self.context!.save() // mainContext
...
//If the user fetches the data I use:
self.backContext.perform {
...
}
//Since I want to show results in UI, I know these objects (from fetch) exist just in the background thread, so instead I fetch for IDs
.resultType = .managedObjectIDResultType
//Now I have IDs of fetched objects, so I restore objects in main thread using their IDs:
let object = try? self.backContext.existingObject(with: ID) as? Record
//and I can finally use fetched objects to update UI.
The question is:
Is this even correct what I am doing? (it works perfectly though)
Will this solve the problem of freezing UI if user fetches a large amount of data?
How do we use backgroundContexts correctly? Why is it not recommended to work directly with mainContext? How to prevent freezing UI while fetching big data?
One more question: If I use FetchedResultsConteroller - do I need to handle the problem of freezing UI? (while waiting on first (init) fetch result?)
Of course I am ignoring that while fetching data, my context is blocked, so I cannot create a new record
If you are fetching objects to be displayed on screen, you should absolutely be using fetch requests against the main thread context. Core Data is designed for this specific use case and you should not be experiencing slowdowns or freezes because of executing fetches. If you are having problems, then profile your app in instruments and find out where the actual slowdown is.
Background contexts are meant to be used if you are performing bulky or long-running work like processing large API responses which you've shown to be affecting main thread performance.
So I do not have to be afraid of freezing UI, even if my database will contain thousands of records? I can make fetch request with mainContext?
Yes
If I would like to do some special time consuming operations that would not be shown to UI, my code would be correct, right?
Yes, you'd normally create a background context, do work, save the background context - and then access those objects as normal from the main context.
And last but not least - why is it not recommended to work directly with mainContext when I add a new record?
I'm not sure where you've seen this recommendation, but quite a common pattern is to make a new main-queue (not background) child context to support the application workflow of adding a new object. Then if the user cancels the addition, you can just discard the editing context without needing to worry about undoing your work.

CoreStore create object in context without saving to database

I want to solve next problem:
I would like to work with some NSManagedObject in context and change some properties in runtime, but without telling SQLite about any changes in it.
I just want to save NSManagedObject to database when I hit save button or similar.
As I found out from source code demo we need to use beginUnsafe for this purposes (maybe I am wrong)
func unstoredWorkout() -> WorkoutEntity {
let transaction = CoreStore.beginUnsafe()
let workout = transaction.create(Into<WorkoutEntity>())
return workout
}
let workout = unstoredWorkout()
workout.muscles = []
Now when I try to update workout.muscles = [] app crashes with error:
error: Mutating a managed object 0x600003f68b60 <x-coredata://C00A3E74-AC3F-47FD-B656-CA0ECA02832F/WorkoutEntity/tC3921DAE-BA43-45CB-8271-079CC0E4821D82> (0x600001c2da90) after it has been removed from its context.
My question how we can create object without saving it and how we can save it then when we modify some properties and avoid this crash as well.
The reason for the crash is that your transaction only lives in your unstoredWorkout() method, so it calles deinit, which resets the context (and deletes all unsaved objects).
You have to retain that unsafe transaction somewhere to keep your object alive - such as in the viewcontroller that will eventually save the changes.
But I would rather encourage you to think about that if you really want to do that. You might run into other synchronization issues with various context or other async transactions alive, like when API calls are involved.

How to handle NSManagedObjectContext and NSManagedObject creation and editing on multiple threads?

I have an application where I’m using Core Data. It’s my first time with it so I’m using the same Core Data stack that Apple provides in the AppDelegate.m .
The problem I’m facing is described below :
I have a method called firstSaver which performs operations as :
+(void) firstSaver {
// 1) get some values from system
// 2) do some processing on those values ( This takes considerable time)
// 3) create a NSManagedObject instance of entity A ,say mObj ,by filling in the processed values. I create multiple objects. In this step, I use the main managedObjectContext that is provided by the AppDelegate to me.
// 4) pass this NSManagedObject to secondSaver like :
[self secondSaver : mObj];
// 5) save the managedObjectContext.
}
the second method works as :
+(void) secondSaver : (NSManagedObject *)someObj {
// 1) again fetch some values, this too takes considerable time.
// 2) create a NSManagedObject which is instance of entity B, fill the processed values, attach this instance to the someObj instance.
return;
}
Note that A is related to B by a one-to-many relationship, i.e. A contains a NSSet of B.
As seen, the two calls require considerable time to complete and it freezes the UI. I don’t want it to happen hence I created a serial dispatch_queue and called the firstSaver on it using dipatch_async.
The problem is that as the instance of NSManagedObjectContext has been created on the main thread, and if I access it inside dispatch_async, it results in EXEC_BAD_ACCESS.
What could possibly be the correct approach to handle this scenario and use proper managed object context for dealing with multithreading ? Any help will be appreciated.
You should create new child managed object contexts to use, with private queue type and the main context as the parent. All of your logic should be in a performBlockAndWait: and that's where you do your long query and create the new object.
To use the mObj here you need to get its objectID and then use existingObjectWithID:error: to get the appropriate version in the child context. Then you can connect your new object to the existing object but in the correct context.
When you're done, save the child context and then use performBlock on the main context and save it.
// move to background thread
// create child
// set parent
// perform block and wait on new context
// find the existing object for mObj ID
// search, create, associate
// save
// perform block on the main context
// save
Multithreadding with Coredata is a pain in the ass. You should avoid this if possible. If the creation or modification of an MO takes long time, create the data or modify the exiting one in a background thread and then do a performSelectorOnMainthread for all Coredata actions.

Detach an Object from a Realm?

Lets say I have the following scenario...
ViewController1 loads a Person object from a Realm on the main thread and passes it to ViewController2. User interaction in ViewController2 causes the same Person object to change, but I only want to persist the changes once the User has pressed "Save".
Currently, when changing the passed Person object in ViewController2 a runtime error is thrown saying changes to an object need to be made in a Write block. This makes sense, but in this scenario I don't actually want to persist the changes right away.
Is there a way to detach an Object from a Realm to avoid these
checks?
If there isn't, what would be a suggested work around? (Copying the Object to a new instance? Tracking the changes to the Object separately and applying them later? Both seem pretty messy.)
Right now, you can make a 'standalone' copy of your object, via Object(value: existingObject) -- that'll probably be the simplest solution for now, until Realm adds something like nested transactions that will make undoing an arbitrary number of changes easier.
Realm has added a freeze() function for Realm Objects on RealmSwift 5.+ versions.
Imagine we have a Realm object Edible with a property name. Previously you weed need to hold a ThreadSafeReference to the object, get a realm on the other thread and unwrap the reference. Meh.
If you didn't, it would crash:
// Code running on main thread
let edible = realm.objects(Edible.self)[0]
DispatchQueue.global(qos: .background) {
let name = edible.name // Realm accessed from incorrect thread error
}
How to do detach an object in RealmSwift 5.+:
let edible = realm.objects(Edible.self)[0].freeze()
DispatchQueue.global(qos: .background) {
let name = edible.name // No longer crashes
}
Keep in mind as of Jan 11 2021, the freeze() function can create a lot of unknown weird errors affecting the users, as mentioned repeatedly in the issues for the new Realm version. For now, stay away from it. Use deep copying.

UIManagedDocument parent context object insertion on a background priority queue not updating in UI using child context

I'm trying to implement some basic UIManagedDocument import/export functionality into my app, mainly for dev so that I can easily inspect the document contents and more crucially preserve a set of test data when I start trying to iterate on my CoreData models.
All I am trying to do is load some JSON data from a local file and inject it into my apps UIManagedDocument. The UIManagedDocument's ManagedObjectContext contents are visualised in my app using some Core Data Table View Controllers from the Stanford iOS courses.
I thought I'd try to write this with some threading to keep the UI responsive and to learn how to do it. So I've done something like this
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
// try to create a new core data entity with my document's ManagedObjectContext
} );
At first I thought this was working. No errors, asserts, crashes triggered and upon looking at my CoreData TableViews I could see the newly added objects in my UI. Unfortunately the newly added objects were seemingly never saved back to the store. I even hooked up to listen to the NSManagedObjectContextDidSaveNotification from my UIDocument's managedObjectContext and saw it wasn't triggering on pressing the home button, like it usually does if it has some changes performed in the app with my UI pending. Infact even doing these operations in the UI wouldn't cause the notification and saving to occur so it was clearly not happy.
I unrolled the code from within the background queue and ran it on the main thread synchronously and everything worked ok, the new data was saved correctly.
I started reading about the complexities of threading and coredata, the documentation seemed to suggest using the UIDocument's ManagedObjectContext's parent ManagedObjectContext to perform operations on in the background so I tried doing the same code again using this parent context, so as follows
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
// try to create a new core data entity with my document's ManagedObjectContext parent ManagedObjectContext
} );
This time for some reason the CoreData TableView controllers no longer updated to show the newly injected objects. Even after explicitly calling save on the parent context, nothing appeared. However on quitting the app and reloading the app the newly injected objects did seem to be added correctly. Interestingly at this point i'd left a fetchrequest with a cachename specified and that threw up an error on this first run of the app after injecting the objects this way. I guess somehow the way the object had come from the parent context directly perhaps invalidated the cache somehow, that's still something I don't fully understand. Even changing the cache to nil didn't fix the issue of the table views not updated the same session as when the objects were injected into the parent context.
Looking elsewhere I've seen some uses of the managedObjectContext performBlock suggested. Another case where someone has said you must call
[document updateChangeCount:UIDocumentChangeDone]
after all changes to ensure the saving is performed, or perhaps using
- (void)autosaveWithCompletionHandler:(void (^)(BOOL success))completionHandler
instead. Though elsewhere I've seen mentioned that saving should be enough to push context contents through the hierarchy. Does saving only work from child -> parent and not from parent -> child.
Or am I just doing it wrong?
Anyone's time and help is really appreciated! Cheers.
please look at the 'Parent/Child Contexts' section in the Multi-Context CoreData.
Whenever a child MOC saves the parent learns about these changes and this causes the fetched results controllers to be informed about these changes as well. This does not yet persist the data however, since the background MOCs don’t know about the PSC. To get the data to disk you need an additional saveContext: on the main queue MOC.
I do saving to the PSC n following way:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
NSError* error;
if (self.managedObjectContext.hasChanges)
{
[self.managedObjectContext save: &error];
}
// Save main context
dispatch_sync(dispatch_get_main_queue(), ^
{
[[AXContextHelper sharedInstance] saveMainContext];
});
}
After looking into all the suggestions I could find for similar problems, none of them seemed to help.
The scenario i was describing in the end had the store stuff to file handled ok, but the UI not updating. At the time when I do this import/export I'm in a view ontop of the core data table view controller that doesn't update when I inject all these JSON objects, so in the end all I did was force that controller to re-fetch it's data. I believe the NSFetchedResultsController is meant to monitor the managedObjectContext and update the fetch request as required. For whatever reason, this wasn't working.
With the force re-fetch called, everything seems to work ok. The newly injected entities appear in my tables, and are saved to the store files.
Either there are some bugs in these objects, or I'm still using it wrong but I see other problems... my import/export data both work now on background threads with the parent context. What I've noticed today is that I can use the UI to properly insert and edit some objects using the child context on the main thread. I don't deliberately call anything at the moment after making these edits so i guess the edits are are still pending till core data decides to save. If i then go to my export feature and use it using the parent context then i find the newly edited or inserted objects aren't there.
Clearly there's no messaging happening in time from child -> parent, and messaging from background thread edits on the the parent itself just doesn't seem to work.. I never get any notifications for the edits i perform in a thread on the parent context, even though they seem to work. If I force the save to happen via the documents then the passed in dictionaries show no edits being made, even though the edits are saved correctly to store files somehow.
I guess i now need to force my document to save after every operation, or at least before every operation i'm about to do on another thread to ensure the parent/child are both in sync.
Still the problem I originally described I've managed to get around, but I'm still left wondering quite how I should be using the parent and child context of a UIManagedDocument to avoid encountering these issues.

Resources