user1284151 February 2016

iOS: Synchronizing access to CoreData

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/

Answers


Jonah February 2016

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

Post Status

Asked in February 2016
Viewed 2,331 times
Voted 8
Answered 1 times

Search




Leave an answer