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.locationsproperty 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 1set. 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 1set. I can see in the logs that Core Data is inserting exactly twice as manyLocationrows 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
NSMergePolicyis 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!