获取、解析 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,所以上面的应该可以正常工作。