获取、解析 JSON 个对象,然后保存到 Core Data 会产生重复的记录
Fetching, parsing JSON objects, then saving to Core Data produces duplicated records
作为一个 Swift 新手,我正在尝试获取 JSON 对象的列表,将它们保存到 Core Data,然后显示在 SwiftUI 列表中:
JSON 对象有一个唯一的数字 uid
(在我的 PostgreSQL 后端充当主键)。
我将它们映射到 TopModel.swift 中的 id
:
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?
}
然后我有一个单例 DownloadManager.swift,它下载 JSON 列表并将对象存储到核心数据中:
func getTopsCombine() {
guard let url = URL(string: "https://slova.de/ws/top") 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)
}
不幸的是,这会导致多个相同的记录被保存在核心数据中(如您在上面的屏幕截图中所见)。
此处的标准 iOS 解决方案是什么?
如何加强唯一性(在我原来的 Android/Room 应用程序中,我使用 @PrimaryKey public int uid;
)?我在 Xcode:
中没有看到这样的选项
以及如何合并获取的 JSON 对象列表(在我的 Android 应用程序中,我使用 DiffUtils)?
在 Core Data 中,您必须检查条目是否已存在,创建谓词并获取具有唯一 ID 的对象。如果没有项目创建一个。
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
}
}
如果项目存在,您甚至可以更新它
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
}
如果您不使用 CloudKit,您可以对 Core Data 强制执行唯一约束。如果是,则必须根据 Id 自己进行比较。
要使用唯一约束,您只需将它们添加到受约束实体的数据模型检查器的“约束”部分。请注意,此图像显示了检查器的优先图标,但它仍然是最右边的图标:
如果您打算使用 CloudKit,则不能使用约束,因为 CloudKit 中不允许使用约束。因此,您需要编写一个函数来唯一化您的数据,并在数据写入存储后调用它。听起来你是跨平台的,所以你不会使用 CloudKit,所以上面的应该可以正常工作。
作为一个 Swift 新手,我正在尝试获取 JSON 对象的列表,将它们保存到 Core Data,然后显示在 SwiftUI 列表中:
JSON 对象有一个唯一的数字 uid
(在我的 PostgreSQL 后端充当主键)。
我将它们映射到 TopModel.swift 中的 id
:
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?
}
然后我有一个单例 DownloadManager.swift,它下载 JSON 列表并将对象存储到核心数据中:
func getTopsCombine() {
guard let url = URL(string: "https://slova.de/ws/top") 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)
}
不幸的是,这会导致多个相同的记录被保存在核心数据中(如您在上面的屏幕截图中所见)。
此处的标准 iOS 解决方案是什么?
如何加强唯一性(在我原来的 Android/Room 应用程序中,我使用 @PrimaryKey public int uid;
)?我在 Xcode:
以及如何合并获取的 JSON 对象列表(在我的 Android 应用程序中,我使用 DiffUtils)?
在 Core Data 中,您必须检查条目是否已存在,创建谓词并获取具有唯一 ID 的对象。如果没有项目创建一个。
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
}
}
如果项目存在,您甚至可以更新它
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
}
如果您不使用 CloudKit,您可以对 Core Data 强制执行唯一约束。如果是,则必须根据 Id 自己进行比较。
要使用唯一约束,您只需将它们添加到受约束实体的数据模型检查器的“约束”部分。请注意,此图像显示了检查器的优先图标,但它仍然是最右边的图标:
如果您打算使用 CloudKit,则不能使用约束,因为 CloudKit 中不允许使用约束。因此,您需要编写一个函数来唯一化您的数据,并在数据写入存储后调用它。听起来你是跨平台的,所以你不会使用 CloudKit,所以上面的应该可以正常工作。