Core Data multi thread application
The Apple Concurrency with Core Data documentation is the place to start. Read it really carefully... I was bitten many times by my misunderstandings!
Basic rules are:
- Use one
NSPersistentStoreCoordinator
per program. You don't need them per thread. - Create one
NSManagedObjectContext
per thread. - Never pass an
NSManagedObject
on a thread to the other thread. - Instead, get the object IDs via
-objectID
and pass it to the other thread.
More rules:
- Make sure you save the object into the store before getting the object ID. Until saved, they're temporary, and you can't access them from another thread.
- And beware of the merge policies if you make changes to the managed objects from more than one thread.
NSManagedObjectContext
's-mergeChangesFromContextDidSaveNotification:
is helpful.
But let me repeat, please read the document carefully! It's really worth it!
CoreData and multithreading environment
Do not use @synchronize
with Core Data. You just need to follow the threading rules of Core Data:
- A context can only be used on the queue that it is associated with (Main queue or Private queue)
- A managed object can only be used on the queue that the context that it is associated with belongs to (again Main queue or Private queue)
And my personal rule:
- If it is feeding the UI, use a Main Queue context (preferably a singular main queue context)
- Data processing belongs on a private queue context that is a child of the main queue context.
Follow those rules and you are guaranteed not to have threading issues.
Coredata - Multithreading best way
I prefer having at least 2 contexts.
The main one associated with the persistent store (with no parent contexts) is privateQueueConcurrencyType
so that UI is not affected during the saves to disk.
The second one is viewContext for UI which is the child context for the privateContext.
I usually have another one for background import which is a child context of the UI context and is configured as privateQueueConcurrencyType
so it doesn't block the UI. When saved, the UI gets updated, and then changes are saved to the persistent store (when saved recursively).
Also, I create disposable child contexts to the viewContext whenever I will be making changes. I make the changes in the childContext then save recursively. I find this way saved my butt multiple time in multi-user situations.
Below is my setup:
lazy var privateSaveContext: NSManagedObjectContext = {
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.name = "privateSaveContext"
moc.persistentStoreCoordinator = self.coordinator
moc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return moc
}()
lazy var viewContext: NSManagedObjectContext = {
let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
moc.name = "viewContext"
moc.parent = self.privateSaveContext
moc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return moc
}()
lazy var importContext: NSManagedObjectContext = {
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.name = "importContext"
moc.parent = self.viewContext
moc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return moc
}()
When saving to disk, I save recursively:
class func save(_ moc: NSManagedObjectContext) {
moc.performAndWait {
if moc.hasChanges {
DLog("Inserted objects count = \(moc.insertedObjects.count)")
do {
try moc.save()
if moc.parent == nil { DLog("SAVED changes to persistent store") }
DLog("SAVED context '\(moc)'")
} catch {
DLog("ERROR saving context '\(moc)' - \(error)")
}
} else {
if moc.parent == nil { DLog("SKIPPED saving changes to persistent store, because there are no changes") }
DLog("SKIPPED saving context '\(moc)' because there are no changes")
}
if let parentContext = moc.parent {
save(parentContext)
}
}
}
p.s. DLog
is something I use so that it prints out the function name, date, time etc. You can simply change that to print
.
Core data multithreading fetch record
First, Core Data is thread safe. However you must follow the rules:
NSManagedObjectContext
is thread bound. You can only use it on the thread it is assigned to.-init
causes a context to be assigned to the thread it is created on. Using-initWithConcurrencyType:
will allow you to create contexts associated to other threads/queues.- Any
NSManagedObject
associated with aNSManagedObjectContext
is tied to the same thread/queue as the context it came from - There is no third rule
You can pass NSManagedObjectID
instances between threads but rules 1 and 2 must be obeyed. From your description I believe you are violating these rules.
Personally I do not recommend using NSManagedObjectID either. There are better solutions. – Marcus S. Zarra
Marcus, this is the most succinct explanation of Core Data's threading I've read. Having used it since it's introduction, there are days I still get these rules wrong! You mention "better solutions" — can you elaborate?
I have a fairly strong distrust of the use of the NSManagedObjectID
. It does not stay the same from one application lifecycle to another in many situations. Originally, based on the documentation, we (Cocoa developers in general) believed it was our mythical primary key being generated for us. That has turned out to be incorrect.
In modern development with parent/child contexts the landscape is even more confusing and there are some interesting traps that we need to watch out for. Given the current landscape I dislike it more than I did previously. So what do we use?
We should generate our own. It doesn't need to be much. If your data does not have a primary key from the server already (pretty common to have an id
from a Ruby based server) then create one. I like to call it guid
and then have an -awakeFromInsert
similar to this:
- (void)awakeFromInsert
{
[super awakeFromInsert];
if (![self primitiveValueForKey:@"guid"]) {
[self setPrimitiveValue:[[NSProcessInfo processInfo] globallyUniqueString] forKey:@"guid"];
}
}
NOTE: This code is written in the web browser and may not compile.
You check the value because -awakeFromInsert
is called once per context. Then I will generally have a convenience method on my NSManagedObject
instances similar to:
@implementation MyManagedObject
+ (MyManagedObject*)managedObjectForGUID:(NSString*)guid inManagedObjectContext:(NSManagedObjectContext*)context withError:(NSError**)error
{
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:[self entityName]];
[fetch setPredicate:[NSPredicate predicateWithFormat:@"guid == %@", guid]];
NSArray *results = [context executeFetchRequest:request error:error];
if (!results) return nil;
return [results lastObject];
}
@end
NOTE: This code is written in the web browser and may not compile.
This leaves error handling and context/threading control up to the developer but provides a convenience method to retrieve an object on the current context and lets us "bounce" an object from one context to another.
This is slower than -objectWithID:
and should be used carefully and only in situations where you need to bounce an object from one context to another after a save moves it up the stack.
As with most things I do; this is not a generic solution. It is a baseline that should get adjusted on a per project basis.
Core Data operations on multiple thread causing insertion but no fetching
so I am using core data on multiple threads in order to be thread-safe
What do you mean by this? Using multiple threads doesn't make anything thread-safe. Thread safety relates to your ability to run your code on multiple threads without problems, and it generally requires that you take a number of precautions to prevent threads from interfering with each other.
The problem is that I am able to insert Data in CoreData, but while fetching from CoreData, the results are zero, this is happening when I kill my app and fetch the data from Database. This has something to do with NSMangedObjectContext but I am not able to figure it out.
You need to understand what a managed object context is. Think of it like a temporary workspace: you can execute a fetch request to bring objects from a persistent store into a managed object context, and you can add new objects to the context, and you can manipulate the objects in a context. The changes you make in a context don't mean anything outside the context until you save the context back to the persistent store.
A few reasons you might not be seeing the objects you're adding are:
You're adding the objects and trying to read them back in different contexts.
You never save the context after you add objects.
You save the context in which you added the object, but the parent context (managed object contexts are hierarchical) is never saved.
You try to save the context after you add objects, but saving fails.
You're using the same context in multiple threads without taking care to serialize the operations on the context (which is to say, your code isn't thread-safe).
What you really should do to figure this out is to get yourself back to a state where you can store and retrieve objects reliably. Try using just one thread and make sure that your operations work. If they don't, fix that first. Next, get a solid understanding of how managed object contexts work and how to use them. Finally, read up on concurrency and Core Data.
How does Core data concurrency work with multithreading in Swift 3?
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 Topics
Uncaught Exception: This Class Is Not Key Value Coding-Compliant
Add "Edit in Excel" or "Edit Photo" Extension
What Exactly Can Corebluetooth Applications Do Whilst in the Background
How to Allow User to Pick the Image with Swift
How to Apply Multiple Transforms in Swift
Making a Phone Call in an iOS Application
How to Change the Uinavigationcontroller Back Button Name
How to Maintain Presenting View Controller's Orientation When Dismissing Modal View Controller
iOS Protocol/Delegate Confusion
Test Whether a Uiview Is in the Middle of Animation
Swiftui Datepicker Binding Optional Date, Valid Nil
Add a Uiview Above All, Even the Navigation Bar
Since Xcode 8 and iOS10, Views Are Not Sized Properly on Viewdidlayoutsubviews
How Does Cellforrowatindexpath Work
Ios7 iPad Landscape Only App, Using Uiimagepickercontroller