We have an iOS application that uses Core Data to persist records fetched from a private web API. One of our API requests fetches a list of Project
records, each of which has multiple associated Location
records. ObjectMapper is used to deserialize the JSON response, and we have a custom transformer that assigns the nested Location
attributes to a Core Data association on the Project
entity.
The relevant part of the code looks like this. It's executed within a PromiseKit promise (hence the seal
), and we save first to a background context and then propagate to the main context that gets used on the UI thread.
WNManagedObjectController.backgroundContext.perform {
let project = Mapper<Project>().map(JSONObject: JSON(json).object)!
try! WNManagedObjectController.backgroundContext.save()
WNManagedObjectController.managedContext.performAndWait {
do {
try WNManagedObjectController.managedContext.save()
seal.fulfill(project.objectID)
} catch {
seal.reject(error)
}
}
}
The problem we're having is that this insert process is saving each Location
record to the database twice. Strangely, the duplicated Location
records don't have any association with their parent Project
record. That is to say, if Location
records are looked up with an NSFetchRequest
, or if I run a query on the underlying SQLite database, I can see that there are two entries for each Location
, but project.locations
only returns one copy of each Location
. The same (or very similar) process applied to other record types with the same structure also results in duplicates.
I've tried several things so far to narrow down the problem:
- Inspected the API JSON - no duplicates.
- Inspected the state of the
project.locations
property immediately before the Core Data write. No duplicate records are present prior to the objects being persisted, indicating that the deserializer and custom nested attributes transformer are working correctly. - Removed the block that propagates the changes to the main thread managed object context, in case this was causing the insert to occur twice. Still get duplicates with solely the write to the background context.
- Run the app with
com.apple.CoreData.ConcurrencyDebug 1
set. No exception is thrown in this process, confirming that it's not a thread safety issue of some kind. - Run the app with
com.apple.CoreData.SQLDebug 1
set. I can see in the logs that Core Data is inserting exactly twice as manyLocation
rows as expected into the underlying SQLite database. - Implemented a uniqueness constraint on the entity. This fixes the problem in terms of what data gets persisted, but will still throw an error unless an
NSMergePolicy
is set.
The last item in that list effectively solves the problem, but it's treating the symptom, not the cause. Data integrity is important for our application, and I'm looking to understand what the underlying problem might be, or other options I might pursue for investigating it further.
Thanks!