Reputation: 22958
Being a Swift newbie I am trying to fetch a list of JSON objects, save them to Core Data, then display in a SwiftUI List:
The JSON objects have a unique numeric uid
(acting as PRIMARY KEY in my PostgreSQL backend).
I map them to id
in my TopModel.swift:
struct Top: Codable, Identifiable {
var id: Int { uid }
let uid: Int
let elo: Int
let given: String
let photo: String?
let motto: String?
let avg_score: Double?
let avg_time: String?
}
Then I have a singleton DownloadManager.swift, which downloads the JSON list and stores the objects into the Core Data:
func getTopsCombine() {
guard let url = URL(string: "https://wordsbyfarber.com/ru/top-all") else { return }
URLSession.shared.dataTaskPublisher(for: url)
.subscribe(on: DispatchQueue.global(qos: .background))
.receive(on: DispatchQueue.main)
.tryMap(handleOutput)
.decode(type: TopResponse.self, decoder: JSONDecoder())
.sink{ ( completion ) in
print(completion)
} receiveValue: { [weak self] (returnedTops) in
for top in returnedTops.data {
print(top)
let topEntity = TopEntity(context: PersistenceController.shared.container.viewContext)
topEntity.uid = Int32(top.id)
topEntity.elo = Int32(top.elo)
topEntity.given = top.given
topEntity.motto = top.motto
topEntity.photo = top.photo
topEntity.avg_score = top.avg_score ?? 0.0
topEntity.avg_time = top.avg_time
}
self?.save()
}
.store(in: &cancellables)
}
Unfortunately, this results in multiple identical records being saved in the Core Data (and as you can see in the above screenshot).
What is the standard iOS solution here?
How to enforce the uniqueness (in my original Android/Room app I use @PrimaryKey public int uid;
for that)? I do not see such an option in Xcode:
And how to merge fetched list of JSON objects (in my Android app I use DiffUtils)?
Upvotes: 3
Views: 1240
Reputation: 285059
In Core Data you have to check if the entry already exists, create a predicate and fetch the objects with the unique id. If there is no item create one.
for top in returnedTops.data {
let context = PersistenceController.shared.container.viewContext
let request : FetchRequest<TopEntity> = TopEntity.fetchRequest()
request.predicate = NSPredicate(format: "uid == %ld", top.id)
if try? context.fetch(request)?.first == nil {
print(top)
let topEntity = TopEntity(context: context)
topEntity.uid = Int32(top.id)
topEntity.elo = Int32(top.elo)
topEntity.given = top.given
topEntity.motto = top.motto
topEntity.photo = top.photo
topEntity.avg_score = top.avg_score ?? 0.0
topEntity.avg_time = top.avg_time
}
}
You can even update the item if it exists
for top in returnedTops.data {
let context = PersistenceController.shared.container.viewContext
let request : FetchRequest<TopEntity> = TopEntity.fetchRequest()
request.predicate = NSPredicate(format: "uid == %ld", top.id)
let topEntity : TopEntity
if let result = try? context.fetch(request)?.first {
topEntity = result
} else {
print(top)
topEntity = TopEntity(context: context)
}
topEntity.uid = Int32(top.id)
topEntity.elo = Int32(top.elo)
topEntity.given = top.given
topEntity.motto = top.motto
topEntity.photo = top.photo
topEntity.avg_score = top.avg_score ?? 0.0
topEntity.avg_time = top.avg_time
}
Upvotes: 1
Reputation: 9665
If you are NOT using CloudKit, you can enforce a unique constraint on Core Data. If you are, you will have to make the comparison yourself based off the Id.
For using unique constraints, you simply add them in to the "Constraints" section of the Data Model Inspector for the entity constrained. Note this image shows the prior icon for the inspector, but it is still the far right icon:
If you are planning on using CloudKit, you can't use constraints because they are not allowed in CloudKit. Therefore, you need to write a function that uniques your data and call it after the data has been written to storage. It sounds like since you are cross platforming that you won't be using CloudKit, so the above should work fine.
Upvotes: 1