如何使用 CoreData 正确存储和加载数据?

How to store and load data properly with CoreData?

我是一个初学者,从来没有接触过 CoreData。我有一个 JSON 响应,结果需要显示在 Table 视图中。我想为我的项目实现一个 CoreData。

JSON 解析(在单独的 Swift 文件中)

 func parseJSON(with currencyData: Data){
    let decoder = JSONDecoder()

    do {
        let decodedData = try decoder.decode(CurrencyData.self, from: currencyData)
        
        for valute in decodedData.Valute.values {
            if valute.CharCode != "XDR" {
                 let currency = Currency(context: self.context)
                currency.shortName = valute.CharCode
                currency.currentValue = valute.Value
            }
        }

         do {
             try context.save()
         } catch {
             print("Error saving context, \(error)")
         }
    } catch {
        self.delegate?.didFailWithError(self, error: error)
        return
    }
}

在我的 VC 中,我想将它加载到我的 tableView 中,它从 currencyArray:

    func loadCurrency() {
    let request: NSFetchRequest<Currency> = Currency.fetchRequest()
    do {
        currencyArray = try context.fetch(request)
    } catch {
        print(error)
    }
    tableView.reloadData()
}

我开始解析并在我的 VC 中加载货币数据:

 override func viewDidLoad() {
    super.viewDidLoad()
    currencyNetworking.performRequest()
    loadCurrency()
}

但是当应用程序第一次启动时,我的 tableView 是空的。第二次发射 - 我的数据翻了一番。也许这是因为 loadCurrency() 在 performRequest() 之前启动,能够从 JSON 接收数据并将其保存到上下文中。

我也尝试不在 parseJSON() 中保存上下文,但首先通过委托方法将其发送到 VC 并执行保存到上下文并从此处从上下文加载。然后 tableView 从第一次启动加载。但是每次应用程序在我的 CoreData 数据库中启动时,我都会看到相同数据的增加(33、66、99 行)。

我的目标是将已解析的数据保存到 CoreData 一次(在我解析的地方JSON 或在 VC 中)并在用户想要更新时仅更改 currentValue 属性。

如何改正?

您需要首先选择显示数据的真实来源。在你的情况下,它似乎是你的本地数据库,它是由一些外部来源填充的。

如果您希望将本地数据库与远程服务器提供的数据一起使用,那么您需要一些信息来跟踪“相同”条目。这通常是通过使用 id 来实现的,这是一个标识符,它在条目之间是唯一的,并且在条目的变化中持久存在。可以使用与这些规则相对应的任何其他 属性。例如,如果总是只有其中一个,CharCode 对你的情况可能就足够了。

接下来您可能需要一个 deleted 标志,这意味着您还可以从服务器中删除项目,当您找到 deleted 标志设置为 true 的条目时,您需要删除此对象。

现在你从服务器获取物品时的伪代码你应该这样做:

func processEntries(_ remoteEntries: [CurrencyEntry]) {
    remoteEntries.forEach { remoteEntry in
        if remoteEntry.isDeleted {
            if let existingLocalEntry = database.currencyWithID(remoteEntry.id) {
                existingLocalEntry.deleteFromDatabase()
            }
        } else {
            if let existingLocalEntry = database.currencyWithID(remoteEntry.id) {
                database.updateCurrency(existingLocalEntry, withRemoteEntry: remoteEntry)
            } else {
                database.createCurrency(fromRemoteEntry: remoteEntry)
            }
        }
    }
}

这只是为了同步方法。


现在转到您的视图控制器。当它出现时,您调用从服务器重新加载数据,同时显示数据库中的任何内容。这一切都很好,但是一旦有新数据可用,您就错过了重新显示。

您最需要的是另一个挂钩,当数据库发生更改时,您的视图控制器将收到通知。因此,最好的办法是在数据库更改时添加事件,即每当您保存数据库时。你可能正在看这样的东西:

extension Database {
    
    func save() {
        guard context.hasChanges else { return }
        try? context.save()
        self.notifyListenersOfChangesInDatabase()
    }

并且此方法将在您的 parseJSON 和您保存数据库的任何其他地方调用。然后你会添加你的视图控制器作为你的数据库的监听器

override func viewDidLoad() {
    super.viewDidLoad()
    database.addListener(self)
    currencyNetworking.performRequest()
    loadCurrency()
}

侦听器和数据库究竟如何连接由您决定。我会建议使用 delegate 方法,但这意味着更多的工作。所以另一种更简单的方法是使用 NotificationCenter ,它应该有很多在线示例,包括 Whosebug。结果将是

  • 而不是 database.addListener(self) 你有 NotificationCenter.default.addObserver...
  • 而不是 notifyListenersOfChangesInDatabase 你使用 NotificationCenter.default.post...

在任何情况下,您都会在视图控制器中获得一个方法,只要您的数据库发生更改就会触发该方法。在那个方法中你调用 loadCurrency。这非常惊人,因为现在您不关心谁更新了您数据库中的数据,为什么以及何时更新。每当发生更改时,您都会重新加载数据并且用户会看到更改。例如,您可以有一个计时器,每隔几分钟收集一次新数据,一切都会正常运行,而无需您进行任何更改。


您可以做的另一种方法是简单地向您的 performRequest 方法添加一个闭包,该方法在您的请求完成后立即触发。这样你的代码应该像

override func viewDidLoad() {
    super.viewDidLoad()
    
    loadCurrency()
    currencyNetworking.performRequest {
        self.loadCurrency()
    }        
}

请注意 loadCurrency 被调用了两次。这意味着我们首先要显示存储在本地数据库中的任何内容,以便用户或多或少立即看到他的数据。同时我们向远程服务器发出请求,这可能需要一段时间。一旦服务器响应完成处理,就会重新加载,用户可以查看更新后的数据。

您的方法 performRequest 可能如下所示:

func performRequest(_ completion: @escaping () -> Void) {
    getDataFromRemoteServer { rawData in
        parseJSON(with: rawData)
        completion()
    }
}