Core Data Multi Thread Application

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:

  1. Use one NSPersistentStoreCoordinator per program. You don't need them per thread.
  2. Create one NSManagedObjectContext per thread.
  3. Never pass an NSManagedObject on a thread to the other thread.
  4. Instead, get the object IDs via -objectID and pass it to the other thread.

More rules:

  1. 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.
  2. And beware of the merge policies if you make changes to the managed objects from more than one thread.
  3. 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:

  1. 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.
  2. Any NSManagedObject associated with a NSManagedObjectContext is tied to the same thread/queue as the context it came from
  3. 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



Leave a reply



Submit