【问题标题】:SwiftUI, Combine, and Core Data -- Items not being mapped/displayed properlySwiftUI、Combine 和 Core Data——项目未正确映射/显示
【发布时间】:2021-04-29 15:49:49
【问题描述】:

我是 SwiftUI 和 Combine 的新手,并且有一个用于测试的简单项目。我正在使用this article 中概述的CDPublisher 类来创建Core Data 和Combine 之间的桥梁。我已经声明了一个名为ItemEntity 的核心数据实体类。它有一个属性,String,包含一个序列化的 JSON 对象。该对象反序列化为一个名为Item 的结构。我的 SwiftUI 视图只显示从我的 ViewModel 返回的项目列表:

struct ContentView: View {
    
    @ObservedObject
    var viewModel: MyListViewModel
    
    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.items, id: \.self.id) { item in
                    Text("\(item.name) - \(item.createdDate)")
                }
            }
            .onAppear {
                self.viewModel.fetchItems()
            }
            .toolbar {
                Button(action: addItem) {
                    Label("Add Item", systemImage: "plus")
                }
            }
        }
    }
    
    private func addItem() {
        let entity = ItemEntity(context: viewContext)
        let rand = arc4random()
        var model = Item(name: "Item \(rand)", createdDate: Date())
        model.id = UUID().uuidString
        let jsonData = try! JSONEncoder().encode(model)
        
        entity.modelJSON = String(data: jsonData, encoding: .utf8)
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("\(nsError) - \(nsError.userInfo)")
        }
    }
}

我的ViewModel 使用CDPublisher 类从Core Data 中获取实体。它还有一个 map 函数,可以将每个条目中的 JSON 字符串反序列化为 Item 实例。最终,Item 对象中的Array 是通过我的ViewModel 提供的。它看起来像这样:

import Foundation
import CoreData
import Combine
import SwiftUI

class MyListViewModel: ObservableObject {
    
    private var viewContext: NSManagedObjectContext =  PersistenceController.shared.container.viewContext
    
    @Published
    var items: [Item] = []
    
    private var cancellables = [AnyCancellable]()
    
    func fetchItems() {
        print("FETCHING...")
        
        let decoder = JSONDecoder()
        let fetchReq: NSFetchRequest<ItemEntity> = ItemEntity.fetchRequest()
        CoreDataPublisher(request: fetchReq, context: self.viewContext)
            .map({ (entities: [ItemEntity]) -> [Item] in
                var items: [Item] = []
                entities.forEach { (entity: ItemEntity) in
                    if let json = entity.modelJSON?.data(using: .utf8) {
                        if let item = try? decoder.decode(Item.self, from: json) {
                            items.append(item)
                        }
                    }
                }
                
                return items
            })
            .receive(on: DispatchQueue.main)
            .replaceError(with: [])
            .eraseToAnyPublisher()
            .sink { completion in
                print("COMPLETION : \(completion)")
            } receiveValue: { items in
                print("SUCCESS")
                items.forEach { (item) in
                    print("\t\(item)")
                }
                self.items = items
            }.store(in: &cancellables)
    }
}

我的代码编译并运行成功。我看到打印语句源自我的 fetchItems() 方法,表明我的 items 数组包含所有预期的对象:

FETCHING...
SUCCESS
    Item(name: "Item 854277542", createdDate: 2021-01-25 19:19:16 +0000)
    Item(name: "Item 92334228", createdDate: 2021-01-25 19:19:17 +0000)
    Item(name: "Item 405319813", createdDate: 2021-01-25 19:19:18 +0000)
    Item(name: "Item 121330574", createdDate: 2021-01-25 19:19:18 +0000)
    Item(name: "Item 3025980536", createdDate: 2021-01-25 19:19:19 +0000)
    Item(name: "Item 1278077958", createdDate: 2021-01-25 19:19:19 +0000)
    Item(name: "Item 4274618146", createdDate: 2021-01-25 19:19:19 +0000)
    Item(name: "Item 2320455869", createdDate: 2021-01-25 19:19:19 +0000)
    Item(name: "Item 3542559526", createdDate: 2021-01-25 19:19:22 +0000)
    Item(name: "Item 4217121551", createdDate: 2021-01-25 19:19:23 +0000)
    Item(name: "Item 4139555338", createdDate: 2021-01-25 19:19:24 +0000)
    Item(name: "Item 1345067436", createdDate: 2021-01-25 19:20:49 +0000)

但是,我的 UI 没有按预期显示项目。我希望每个 Item 对象都有一行。相反,我看到多行,但它们都具有与我的数组中的 first Item 匹配的完全相同的文本。它本质上是一遍又一遍地重复的同一行。

我显然在这里做错了什么,但我没有足够的经验来确切地知道是什么。为什么我的 items 属性具有正确的值,但我没有在我的 UI 中看到它?是因为我的地图功能和 JSON 解码吗? (另外,有没有更好的方法来完成将ItemEntity 实体数组映射到Item 对象数组?)有什么提示吗?

更新:下面是我的 Item 结构的定义

import Foundation

struct Item {
    
    var name: String = ""
    var createdDate: Date = Date()
    
}

extension Item: Codable {
    
}

extension Item: Identifiable {
    private struct IdentifiableHolder {
        static var _id: String = ""
    }
    
    var id: String {
        get {
            return IdentifiableHolder._id
        }
        set(newId) {
            IdentifiableHolder._id = newId
        }
    }
}

【问题讨论】:

  • 请出示物品的声明。我的猜测是您对 Item 的定义有问题 - 只是猜测,因为它没有显示在您的问题中。您拥有显示的代码无法确保您解码的 Item 对象具有唯一的 id 值 — 但 List 取决于这些。
  • @matt 添加了 Item 对象。
  • 是的,好吧,我仍然看不到您解码的 Item 对象(在组合管道中)如何以 "" 以外的任何东西作为它们的 id。这可以解释问题,不是吗?就我个人而言,我倾向于给 Item 一个初始化器,这样通过提供名称和日期来制作 Item 也将自动应用唯一的id

标签: swift core-data swiftui combine


【解决方案1】:

您为 fetchItems 显示的代码无法确保您解码的 Item 对象具有唯一的 id 值——但 List 明确依赖于这些 (id: \.self.id)。相反,您的所有 Item 对象都有默认的id,即""。因此everyItem 对象的id第一个Item 对象的id,解释了这种现象。

你可能一直在追求这样的事情:

struct Item : Codable {
    var name: String = ""
    var createdDate: Date = Date()
    private var idHolder = IdentifiableHolder(id: UUID().uuidString)
}

extension Item: Identifiable {
    private struct IdentifiableHolder : Codable {
        var id: String = ""
    }
    var id: String {
        get {
            return idHolder.id
        }
        set(newId) {
            idHolder.id = newId
        }
    }
}

现在每个项目都有一个唯一的 ID,并且作为其 Codable 实现的一部分进行存储和检索。

【讨论】:

  • 看来你是对的。我将 id 属性移动到结构本身而不是扩展中,它按预期工作。有趣的。我不能通过扩展添加可 JSON 编码的属性吗?我认为以这种方式添加它们会导致它们按预期被拾取,但看起来它们不是。有没有办法获得这种行为?
  • 我不知道您现在拥有什么,也不知道“将 id 属性移动到结构本身”是什么意思。我不认为扩展本身与此事有任何关系。我相信我已经回答了这个问题。
  • 如果您查看我发布的 Item 的代码,Item 结构最初只有 name 和 createdDate 属性。 id 属性作为扩展添加。摆脱该扩展并在 Item 结构中创建一个 id 属性本身纠正了这个问题。我假设扩展中的属性将被序列化为 JSON,就像结构本身中的属性一样。看来情况并非如此。
  • 存储属性。按照目前的定义,您本身没有id“财产”;它是一个 computed 属性,一些函数也是如此。此外,故事中没有 IdentifiableHolder 实例;它只是静态方法的包装器,不可编码。同样,它与“扩展”无关。这与您根本没有真正的 id 属性有关。
  • 所做的 唯一与扩展有关的是您不能在扩展中声明存储属性。我已经在我的答案中添加了您可能一直在追求的那种东西。
猜你喜欢
  • 2021-12-08
  • 2016-02-20
  • 1970-01-01
  • 1970-01-01
  • 2016-08-17
  • 1970-01-01
  • 2020-08-02
  • 1970-01-01
  • 2021-08-28
相关资源
最近更新 更多