iOS: Synchronizing access to CoreData - ios

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).

Related

Why do we need mainQueueConcurrencyType in Core Data?

I'm a newbie in Core Data and get confused about which concurrency queue type I should specify when creating a NSManagedObjectContext. When do we want to use mainQueueConcurrencyType? Here is how Apple documented:
Private queue
(NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType):
The context creates and manages a private queue.
Main queue
(NSManagedObjectContextConcurrencyType.mainQueueConcurrencyType): The
context is associated with the main queue, and as such is tied into
the application’s event loop, but it is otherwise similar to a private
queue-based context. You use this queue type for contexts linked to
controllers and UI objects that are required to be used only on the
main thread.
I know there is a NSFetchedResultsController that can be associated with a UITableView, but I'm not going to use this controller. Can anyone show me an example of how to use mainQueueConcurrencyType?
From my understanding, assume we have a NSManagedObject subclass called CoreDataDesk and it will be used to draw desk objects on screen like this:
class CoreDataDesk: NSManagedObject {
var color: UIColor?
}
class DeskView: UIView {
// Create a MOC with mainQueueConcurrencyType first.
// Then fetch a bunch of CoreDataDesk objects to initialize DeskView objects.
required init(desk: CoreDataDesk) {
self.backgroundColor = desk.color
super.init(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
}
}
Is this a correct example of using main concurrency type context to fetch CoreDataDesk objects and draws UI with them? If so, I think we can do fetch CoreDataDesk with private concurrency type context instead, then we initialize another Desk class(which is not NSManagedObject subclass) with CoreDataDesk still in background thread. Finally we switch back to main thread and use Desk to initialize DeskView as follows:
class CoreDataDesk: NSManagedObject {
var color: UIColor?
}
class Desk {
var color: UIColor?
init(coreDataDesk: CoreDataDesk) {
color = coreDataDesk.color
}
}
func fetchCoreDataDesk() {
// Create a MOC with privateQueueConcurrencyType first.
// Then fetch a bunch of CoreDataDesk objects.
DispatchQueue.main.async {
// convert each CoreDataDesk to Desk
}
}
You can do everything with private queue concurrency. You can also pretty much do everything with main queue concurrency. They're different options for different needs and programming styles. Your approach would work but it's not the only way.
Managed objects are not thread safe, so if you fetch one, you need to use it on the same thread where it was fetched. If you plan to use it on the main queue-- for example to update the UI-- then it must be fetched on the main queue. Main queue concurrency supports this. This is true whether or not you use NSFetchedResultsController.
If you want to write a bunch of code to convert between managed objects and non-managed objects or structs (your Desk and CoreDataDesk, for example), that might not be necessary-- if your non-managed objects are thread safe. Core Data doesn't require you to do that, so it's up to you to decide whether to accept the extra complexity in order to avoid main queue concurrency.

CoreData delete object in context

Question:
Does deleting an NSManagedObject have to be done with in context.perform / context.performAndWait block ?
Or is it safe to delete the object outside the block ?
Code:
func delete(something: NSManagedObject, context: NSManagedObjectContext) {
context.performAndWait { //Is context.perform / context.performAndWait required to delete an object ?
context.delete(something)
}
}
My thoughts:
Since this code was being called from different threads (both background / main ) it was better to use context.perform / context.performAndWait.
The context might have been created with a specific concurrent type (main / private queue).
The context's concurrent type would need to match that of the thread (main / background) in which the code was being executed.
The block would ensure it runs ok even if a thread with a different mismatched thread type is executing it.
As my personal experience, use performAndWait, because it will wait until operation done. Anyway, both method will run on it's own thread.(context's thread).
From Documentation:
perform(:) and performAndWait(:) ensure the block operations are
executed on the queue specified for the context. The perform(:)
method returns immediately and the context executes the block methods
on its own thread. With the performAndWait(:) method, the context
still executes the block methods on its own thread, but the method
doesn’t return until the block is executed.
In apple documentation it is being said about „...andWait” it works assyncronious.
However „...andWait” should be used to catch the errors inside of perform block...
Moc.performBlock{
for jsonObject in jsonArray {
let your = actions
}
do {
try moc.save()
moc.performBlockAndWait {
do { try moc.save() }
catch { fatalError(„Failure to save context: (error)”) }
}
}...
Better to do it inside in case you have different values / unused values. In most cases ARC (memory management) should fix it.
You should also read here:
Core Data background context best practice

Does managedObjectContext.object(with:) always refetch data if another (private) managedObjectContext changed and saved it?

(I'm sorry if this question is kind of confusing/imprecise. I'm just learning advanced CoreData usage and I don't know the terminology and stuff very well).
I have a singleton Game that holds certain data you need during the game. For example, you can access currentSite (Site is a CoreData Entity) from there to get the Site the user is currently at:
// I created the Site in a background queue (when the game started), then saved the objectID and here I load the objectID
public var currentSiteObjectID: NSManagedObjectID {
let objectIDuri = UserDefaults.standard.url(forKey: Keys.forGameObject.currentSiteObjectIDURI)!
return appDelegate.persistentContainer.persistentStoreCoordinator.managedObjectID(forURIRepresentation: objectIDuri)!
}
// managedObjectContext is the one running on the main queue
public var currentSite: Site! {
return managedObjectContext.object(with: currentSiteObjectID) as! Site
}
You see, I retrieve the currentSite by using the managedObjectContext.object(with:) method.
The documentation of this method says:
Returns the object for a specified ID.
If the object is not registered
in the context, it may be fetched or returned as a fault. (...)
I'm not quite sure about the following:
// Each Site has resources that you can access like this
print(Game.shared.currentSite!.resourceSet!.iron)
appDelegate.persistentContainer.performBackgroundTask { (context) in
let currentSite = context.object(with: Game.shared.currentSiteObjectID) as! Site
// Here I increase the iron resource
currentSite.resourceSet!.iron += 42
do {
try context.save()
} catch let error as NSError {
fatalError("\(error.debugDescription)")
}
DispatchQueue.main.async {
print(Game.shared.currentSite!.resourceSet!.iron)
}
}
The second print function is using the managedObjectContext of the main queue (which is different to the private one used inside performBackgroundTask {...}).
It actually does print:
50 // the start value
92
My question: Is it guaranteed that managedObjectContext.object(with:) returns the current object (that is up-to-date), even if it has been changed in another context? The documentation says that it will be fetched if it's a new object that's not known to the context.
But what if an object changes?
I'm not sure if it's just a coincidence that the example code from above is working like expected.
Thanks for any help/explanation! I'm eager to learn about this kind of stuff.
No it is not guaranteed. If managed object is already registered in context then it will return this object. What's more, if object with given id (NSManagedObjectId) doesn't exist in persistent store then your app will crash as soon as you try to use any of its properties.

Where should NSManagedObjectContext be created?

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

Object passed by reference will not exist. Swift

I have an array.
var array:[customType] = [] // pseudo code
func Generate_New_Array(){
//initialization of generatedNewArray
array = generatedNewArray
for (index,element) in array{
async_process({
Update_Data_From_Web(&array[index])
})
}
})
}
func Update_Data_From_Web(inout object:customType){
download_process{
object = downloadedData
}
}
The question is , what will should I do if I call Generate_New_Array before Update_Data_From_Web will finish for each of elements. They will store value back to not-existing index in array. How to avoid problems with that.
You have a couple of options:
Make the Generate_New_Array process cancelable, and then cancel the old one before starting the new one.
Make the Generate_New_Array serial so that when you make a subsequent call to this method, it will finish the calls first. For example, you could have this enqueue an operation on a serial queue.
Regardless of which approach you adopt, if this is multithreaded code, make sure you synchronize your interaction with the model object (via GCD queues or locks or whatever).

Resources