如何使用 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()
}
}
我是一个初学者,从来没有接触过 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()
}
}