版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.12.10 星期四 |
前言
数据的持久化存储是移动端不可避免的一个问题,很多时候的业务逻辑都需要我们进行本地化存储解决和完成,我们可以采用很多持久化存储方案,比如说
plist
文件(属性列表)、preference
(偏好设置)、NSKeyedArchiver
(归档)、SQLite 3
、CoreData
,这里基本上我们都用过。这几种方案各有优缺点,其中,CoreData是苹果极力推荐我们使用的一种方式,我已经将它分离出去一个专题进行说明讲解。这个专题主要就是针对另外几种数据持久化存储方案而设立。
1. 数据持久化方案解析(一) —— 一个简单的基于SQLite持久化方案示例(一)
2. 数据持久化方案解析(二) —— 一个简单的基于SQLite持久化方案示例(二)
3. 数据持久化方案解析(三) —— 基于NSCoding的持久化存储(一)
4. 数据持久化方案解析(四) —— 基于NSCoding的持久化存储(二)
5. 数据持久化方案解析(五) —— 基于Realm的持久化存储(一)
6. 数据持久化方案解析(六) —— 基于Realm的持久化存储(二)
7. 数据持久化方案解析(七) —— 基于Realm的持久化存储(三)
8. 数据持久化方案解析(八) —— UIDocument的数据存储(一)
9. 数据持久化方案解析(九) —— UIDocument的数据存储(二)
10. 数据持久化方案解析(十) —— UIDocument的数据存储(三)
11. 数据持久化方案解析(十一) —— 基于Core Data 和 SwiftUI的数据存储示例(一)
12. 数据持久化方案解析(十二) —— 基于Core Data 和 SwiftUI的数据存储示例(二)
13. 数据持久化方案解析(十三) —— 基于Unit Testing的Core Data测试(一)
14. 数据持久化方案解析(十四) —— 基于Unit Testing的Core Data测试(二)
15. 数据持久化方案解析(十五) —— 基于Realm和SwiftUI的数据持久化简单示例(一)
16. 数据持久化方案解析(十六) —— 基于Realm和SwiftUI的数据持久化简单示例(二)
17. 数据持久化方案解析(十七) —— 基于NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例(一)
18. 数据持久化方案解析(十八) —— 基于NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例(二)
开始
首先看下主要内容:
在本教程中,您将学习如何借助批处理插入,持久性历史记录和派生属性的有效
Core Data
使用来改进iOS应用。内容来自翻译。
下面看下写作环境:
Swift 5, iOS 14, Xcode 12
接着就是主要内容了。
Core Data
是已存在很长时间的古老的Apple
框架之一。自从iOS 10
中发布NSPersistentContainer
以来,苹果公司就向Core Data
表示了极大的热爱。最新添加的Core Data
进一步提升了其竞争力。现在有批量插入请求,持久性历史记录和派生属性,这些绝对可以使Core Data
的使用效率更高。
在本教程中,您将通过提高数据存储效率来改进应用程序。您将学习如何:
Create a batch insert request
Query the persistent store’s transaction history
Control how and when the UI updates in response to new data
您可能会在此过程中拯救人类!
注意:本中级教程假定您具有使用Xcode编写iOS应用程序和编写Swift的经验。您应该已经使用过
Core Data
,并对其概念感到满意。如果您想学习基础知识,可以先尝试Core Data with SwiftUI tutorial。
Fireballs
!他们无处不在!有人在注意吗?Fireballs
可能是外星人入侵的最初迹象,也可能是即将来临的大决战的预兆。有人必须保持警惕。这是你的任务。您已经制作了一个应用程序,可以从NASA Jet Propulsion Laboratory (JPL)
下载火球瞄准点,以便将它们分组并报告可疑的火球活动。
打开启动项目。 看你到目前为止有什么。
Exploring Fireball Watch
构建并运行该应用程序,以便您可以了解其工作方式。 该应用程序从JPL
下载最新的火球数据,为每个火球瞄准创建记录并将其存储在Core Data stack
中。 您还可以创建组并将火球添加到组中以进行报告。
启动时,列表将为空,因此请点击Fireballs
列表右上角的刷新按钮。 很快,该列表就会填满。 您可以再次点击以查看它没有为相同数据添加重复记录。 如果您在某些火球单元上向左滑动并删除了一些,然后再次点击刷新,则会看到下载数据后重新创建的那些fireballs
。
如果点击Groups
选项卡,则可以添加一个组。 进行一些分组,然后返回Fireballs
选项卡,然后在列表中点击一个火球。 然后,点击右上角的in-tray
按钮以选择一个或多个包含该火球的组。 当您点击Groups
标签中列出的组列表时,它将向您显示那个组中所有火球的地图。
注意:您可以在此处阅读有关JPLfireball API here的信息。
Examining the Core Data Stack
现在,看看应用程序的Core Data stack
是如何设置的。
打开Persistence.swift
。 您会看到一个名为PersistenceController
的类。 此类处理您的所有Core Data
设置和数据导入。 它使用NSPersistentContainer
创建一个标准的SQLite
存储,或者创建一个用于SwiftUI
预览的内存存储。
persistent container
的viewContext
是应用程序用于获取请求(生成列表数据)的managed object context
。 这是典型的设置。 您的模型中有两个实体(entities)
:Fireball
和FireballGroup
。
PersistenceController
具有fetchFireballs()
,可下载火球数据并调用私有importFetchedFireballs(_ :)
以将所得的FireballData struct
数组导入为Fireball
的managed objects
。 它使用持久性容器的performBackgroundTask(_ :)
作为后台任务来执行此操作。
importFetchedFireballs(_ :)
循环遍历FireballData
数组,创建一个managed object
并保存managed object context
。 由于永久性容器的viewContext
将automaticallyMergesChangesFromParent
设置为true
,因此在应用程序保存所有对象时,这可能会使UI
停滞。 这是一个会使应用感觉很笨拙的问题,是您第一次改进的目标。
Making a Batch Insert Request
报告的火球列表只会越来越大,如果突然出现火球群怎么办? 火球群可能表明可能有外星人着陆点,预示着新的入侵尝试!
您希望初始下载尽可能灵活。 您的应用程序需要快速使您掌握最新数据。 任何暂停,延迟或挂起都是不可接受的。
批量插入可助您一臂之力! 批处理插入请求是一种特殊的持久性存储请求,它允许您将大量数据直接导入到持久性存储中。 您需要一个方法来为此操作创建批量插入请求。 打开Persistence.swift
并将以下方法添加到PersistenceController
:
private func newBatchInsertRequest(with fireballs: [FireballData])
-> NSBatchInsertRequest {
// 1
var index = 0
let total = fireballs.count
// 2
let batchInsert = NSBatchInsertRequest(
entity: Fireball.entity()) { (managedObject: NSManagedObject) -> Bool in
// 3
guard index < total else { return true }
if let fireball = managedObject as? Fireball {
// 4
let data = fireballs[index]
fireball.dateTimeStamp = data.dateTimeStamp
fireball.radiatedEnergy = data.radiatedEnergy
fireball.impactEnergy = data.impactEnergy
fireball.latitude = data.latitude
fireball.longitude = data.longitude
fireball.altitude = data.altitude
fireball.velocity = data.velocity
}
// 5
index += 1
return false
}
return batchInsert
}
此方法采用FireballData
对象数组,并创建一个NSBatchInsertRequest
来插入所有对象。就是这样:
- 1) 您首先创建局部变量以保存当前循环索引和总火球计数。
- 2) 使用
NSBatchInsertRequest(entity:managedObjectHandler :)
创建批处理插入请求。此方法要求您要执行的每个插入都执行一个NSEntity
和一个闭包 —— 每个火球一个。如果是最后一次插入,则闭包必须返回true
。 - 3) 在闭包内部,您首先要检查是否已到达火球数组的末尾,如果返回
true
,则完成请求。 - 4) 在这里插入新数据。使用
NSManagedObject
实例调用该闭包。这是一个新对象,并检查其类型为Fireball
(始终为,但应始终安全),然后设置对象的属性以匹配获取的Fireball
数据。 - 5) 最后,您增加索引并返回
false
,表示插入请求应再次调用闭包。
注意:在
iOS 13
中,当NSBatchInsertRequest
首次发布时,只有一个初始化程序采用了表示所有要插入数据的字典数组。在iOS 14
中,添加了四个新变体,每个变体使用闭包样式的初始化程序以及managed object
或字典。有关更多信息,请参阅 See the Apple documentation for more information。
Batch Inserting Fireballs
这样就完成了请求创建。 现在,您如何使用它? 将以下方法添加到PersistenceController
:
private func batchInsertFireballs(_ fireballs: [FireballData]) {
// 1
guard !fireballs.isEmpty else { return }
// 2
container.performBackgroundTask { context in
// 3
let batchInsert = self.newBatchInsertRequest(with: fireballs)
do {
try context.execute(batchInsert)
} catch {
// log any errors
}
}
}
下面进行细分:
- 1) 首先,请检查是否有实际的工作要做,以确保数组不为空。
- 2) 然后要求
PersistentContainer
使用performBackgroundTask(_ :)
执行后台任务。 - 3) 创建批处理插入请求,然后执行它,捕获可能引发的任何错误。 批处理请求通过一次事务将所有数据插入持久性存储
(persistent store)
中。 由于您的Core Data model
已定义了唯一约束,因此它将仅创建不存在的新记录,并在需要时更新现有记录。
最后一项更改:转到fetchFireballs()
,而不是调用self?.importFetchedFireballs($ 0)
,将其更改为:
self?.batchInsertFireballs($0)
您也可以注释或删除importFetchedFireballs(_ :)
,因为不再需要它。
注意:如果您想知道,批处理插入请求不能设置
Core Data entity relationship
,但是它们将保持现有关系不变。 有关更多信息,请参见使用WWDC2019中的 Making Apps with Core Data。
剩下要做的就是构建并运行!
但是您可能会注意到有些问题。 如果删除火球,然后再次点击刷新按钮,则列表不会更新。 那是因为批处理插入请求将数据插入到持久性存储(persistent store)
中,但是视图上下文(view context)
没有更新,因此它不知道任何更改。 您可以通过重启应用来确认这一点,然后您将看到所有新数据现在都显示在列表中。
以前,您是在后台队列上下文(background queue context)
中创建对象并保存上下文,这会将更改推送到持久性存储协调器(persistent store coordinator)
。保存后台上下文后,它已从持久性存储协调器自动更新,因为您已在视图上下文中将automaticallyMergeChangesFromParent
设置为true
。
持久性存储(persistent store)
请求的部分效率是它们直接在持久性存储上运行,并且避免将数据加载到内存中或生成上下文保存通知。因此,在应用程序运行时,您将需要一种新的策略来更新视图上下文。
Enabling Notifications
当然,在后台更新存储并非不常见。例如,您可能具有一个用于扩展持久性存储(persistent store)
的应用程序扩展,或者您的应用程序支持iCloud
,并且您的应用程序的存储更新来自其他设备的更改。令人高兴的是,iOS
提供了一个通知– NSPersistentStoreRemoteChange
—每当存储更新发生时,该通知就会发送。
再次打开Persistence.swift
并跳转到init(inMemory :)
。在PersistentContainer
上调用loadPersistentStores(completionHandler :)
的行之前,添加以下行:
persistentStoreDescription?.setOption(
true as NSNumber,
forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
添加这一行会导致您的存储在每次更新时生成通知。
现在,您需要以某种方式使用此通知。 首先,向PersistenceController
添加一个空方法,该方法将作为所有更新处理逻辑的占位符:
func processRemoteStoreChange(_ notification: Notification) {
print(notification)
}
您的占位符方法只是将通知打印到Xcode
控制台。
接下来,通过将其添加到init(inMemory :)
的末尾,使用NotificationCenter
发布者订阅通知:
NotificationCenter.default
.publisher(for: .NSPersistentStoreRemoteChange)
.sink {
self.processRemoteStoreChange($0)
}
.store(in: &subscriptions)
每当您的应用收到通知时,它将调用您的新processRemoteStoreChange(_ :)
。
构建并运行,您将看到Xcode控制台中有关每个更新的通知。 尝试刷新火球列表,添加组,删除火球等。 存储的所有更新将生成一条通知。
那么,此通知对您有何帮助? 如果您想保持简单,则只要收到通知就可以刷新视图上下文(view context)
。 但是,有一种更智能,更高效的方法。 这就是您进入持久性历史记录跟踪(persistent history tracking)
的原因。
Enabling Persistent History Tracking
如果启用持久性历史记录跟踪(persistent history tracking)
,则Core Data
会保留持久性存储中发生的所有事务的事务处理历史记录。 这使您可以查询历史记录,以准确查看更新或创建了哪些对象,并将仅那些更改合并到视图上下文中。
要启用持久性历史记录跟踪,请将此行添加到init(inMemory :)
中,紧接在PersistentContainer
上调用loadPersistentStores(completionHandler :)
的行之前:
persistentStoreDescription?.setOption(
true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
就这些! 现在,该应用程序会将每次更改的交易历史记录保存到您的持久性存储中,您可以通过提取请求查询该历史记录。
Making a History Request
现在,当您的应用收到存储的远程更改通知时,它可以查询存储的历史记录以发现更改内容。 由于存储更新可能来自多个来源,因此您将需要使用串行队列来执行工作。 这样,如果同时发生多组变更,您将避免冲突或竞争条件。
在init(inMemory :)
之前将队列属性添加到您的类中
private lazy var historyRequestQueue = DispatchQueue(label: "history")
现在,您可以返回到processRemoteStoreChange(_ :)
,删除print()
语句并添加以下将执行历史记录请求的代码:
// 1
historyRequestQueue.async {
// 2
let backgroundContext = self.container.newBackgroundContext()
backgroundContext.performAndWait {
// 3
let request = NSPersistentHistoryChangeRequest
.fetchHistory(after: .distantPast)
do {
// 4
let result = try backgroundContext.execute(request) as?
NSPersistentHistoryResult
guard
let transactions = result?.result as? [NSPersistentHistoryTransaction],
!transactions.isEmpty
else {
return
}
// 5
print(transactions)
} catch {
// log any errors
}
}
}
这是上面代码中发生的事情:
- 1) 您可以将此代码作为历史队列中的一个
block
运行,以串行方式处理每个通知。 - 2) 要执行此工作,请创建一个新的后台上下文
(background context)
,并使用performAndWait(_ :)
在该新上下文中运行一些代码。 - 3) 您可以使用
NSPersistentHistoryChangeRequest.fetchHistory(after :)
返回NSPersistentHistoryChangeRequest
,它是NSPersistentStoreRequest
的子类,可以执行以获取历史交易数据。 - 4) 您执行请求,并将结果强制进入
NSPersistentHistoryTransaction
对象数组。历史记录请求的默认结果类型就是这样的对象数组。这些对象还包含NSPersistentHistoryChange
对象,它们是与返回的事务相关的所有更改。 - 5) 您将在此处处理更改。现在,您只需将返回的事务打印到控制台。
构建并运行并执行常规的测试:点按“刷新”按钮,删除一些火球,然后再次刷新等等。您会发现通知已到达,并且一系列事务对象已打印到Xcode控制台。
Revealing a Conundrum: Big Notifications
这揭示了一个难题,如果您已经注意到它,那就做得好!
永久存储的任何更改都会触发通知,即使您的用户从用户交互中添加或删除managed object
也是如此。 不仅如此:请注意,您的历史记录提取请求还会返回事务日志开头的所有更改。
您的通知也太大太多啦!
您的意图是避免对视图上下文(view context)
进行任何不必要的工作,控制何时刷新视图上下文。 完全没有问题,您已经覆盖了它。 为了使整个过程清晰明了,您将通过几个易于遵循的步骤来做到这一点。
1. Step 1: Setting a Query Generation
第一步 —— (迈向控制视图上下文(view context)
的一个小步骤)是设置查询生成(query generation)
。 在Persistence.swift
中,将其添加到NotificationCenter
发布者之前的init(inMemory :)
中:
if !inMemory {
do {
try viewContext.setQueryGenerationFrom(.current)
} catch {
// log any errors
}
}
您将通过调用setQueryGenerationFrom(_ :)
将视图上下文固定到持久性存储(persistent store)
中的最新事务。 但是,由于设置query generation
仅与SQLite
存储兼容,因此仅当inMemory
为false
时才这样做。
2. Step 2: Saving the History Token
您的历史记录请求使用日期来限制结果,但是有更好的方法。
NSPersistentHistoryToken
是一个不透明的对象,用于标记persistent store's transaction history
中的位置。 从历史记录请求返回的每个交易对象都有一个token
。 您可以存储它,以便在查询持久性历史记录时知道从哪里开始。
您将需要一个属性,用于存储在应用程序运行时使用的token
,一种将token
另存为磁盘上文件的方法,以及从已保存的文件加载token
的方法。
在historyRequestQueue
之后,将以下属性添加到PersistenceController
:
private var lastHistoryToken: NSPersistentHistoryToken?
这样会将token
存储在内存中,当然,您需要一个位置将其存储在磁盘上。 接下来,添加此属性:
private lazy var tokenFileURL: URL = {
let url = NSPersistentContainer.defaultDirectoryURL()
.appendingPathComponent("FireballWatch", isDirectory: true)
do {
try FileManager.default
.createDirectory(
at: url,
withIntermediateDirectories: true,
attributes: nil)
} catch {
// log any errors
}
return url.appendingPathComponent("token.data", isDirectory: false)
}()
当您第一次访问该属性时,tokenFileURL
将尝试创建存储目录。
接下来,添加一种将history token
作为文件保存到磁盘的方法:
private func storeHistoryToken(_ token: NSPersistentHistoryToken) {
do {
let data = try NSKeyedArchiver
.archivedData(withRootObject: token, requiringSecureCoding: true)
try data.write(to: tokenFileURL)
lastHistoryToken = token
} catch {
// log any errors
}
}
此方法将token
数据存档到磁盘上的文件中,并更新lastHistoryToken
。
返回到processRemoteStoreChange(_ :)
并找到以下代码:
let request = NSPersistentHistoryChangeRequest
.fetchHistory(after: .distantPast)
使用下面进行替换:
let request = NSPersistentHistoryChangeRequest
.fetchHistory(after: self.lastHistoryToken)
从token
的上次更新以来,这仅从请求整个历史变为请求历史。
接下来,您可以从返回的事务数组中的最后一个事务中获取history token
并进行存储。 在print()
语句下,添加:
if let newToken = transactions.last?.token {
self.storeHistoryToken(newToken)
}
构建并运行,观察Xcode控制台,然后点击“刷新”按钮。 第一次您应该从头开始查看所有交易。 第二次您应该看到的更少了,也许没有。 既然您已经下载了所有火球并存储了最后的交易历史记录token
,那么可能没有较新的交易记录了。
除非有新的火球发现!
3. Step 3: Loading the History Token
当您的应用启动时,您还希望它加载最后保存的历史token
(如果存在),因此将此方法添加到PersistenceController
:
private func loadHistoryToken() {
do {
let tokenData = try Data(contentsOf: tokenFileURL)
lastHistoryToken = try NSKeyedUnarchiver
.unarchivedObject(ofClass: NSPersistentHistoryToken.self, from: tokenData)
} catch {
// log any errors
}
}
如果磁盘上的token
数据存在,此方法将取消存档,并设置lastHistoryToken
属性。
通过将其添加到init(inMemory :)
的末尾来调用此方法:
loadHistoryToken()
构建并运行并再次查看控制台。 不应有新交易。 这样,您的应用程序便可以立即查询历史记录日志!
4. Step 4: Setting a Transaction Author
您可以进一步完善历史记录处理。 每个Core Data managed object context
都可以设置transaction author。transaction author
存储在历史记录中,并成为一种识别每个变更来源的方法。 通过这种方式,您可以直接从后台导入import
过程所做的更改中分辨出用户所做的更改。
首先,在PersistenceController
的顶部,添加以下静态属性:
private static let authorName = "FireballWatch"
private static let remoteDataImportAuthorName = "Fireball Data Import"
这是您将用作作者名称的两个静态字符串。
注意:如果要记录交易记录,请务必有一位上下文作者,这一点很重要。
接下来,在设置viewContext.automaticallyMergesChangesFromParent
的调用的正下方添加以下内容到init(inMemory :)
行中:
viewContext.transactionAuthor = PersistenceController.authorName
这将使用您刚创建的静态属性设置view context
的transaction author
。
接下来,向下滚动至batchInsertFireballs(_ :)
,然后在传递给performBackgroundTask(_ :)
的闭包内,在开头添加以下行:
context.transactionAuthor = PersistenceController.remoteDataImportAuthorName
这会将用于将数据导入到其他静态属性的后台上下文的transaction author
设置。 因此,现在根据对上下文的更改记录的历史记录将具有可识别的来源,而且重要的是,它不同于用于UI更新的transaction author
,例如通过滑动行进行删除。
5. Step 5: Creating a History Request Predicate
要过滤掉由用户引起的任何交易,您需要添加带有谓词的提取请求。
找到processRemoteStoreChange(_ :)
并在执行do
之前添加以下内容:
if let historyFetchRequest = NSPersistentHistoryTransaction.fetchRequest {
historyFetchRequest.predicate =
NSPredicate(format: "%K != %@", "author", PersistenceController.authorName)
request.fetchRequest = historyFetchRequest
}
首先,使用类属性NSPersistentHistoryTransaction.fetchRequest
创建一个NSFetchRequest
并设置其谓词。 如果transaction author
不是您创建的用于识别用户交易的字符串,则谓词测试将返回true
。 然后,使用此谓词获取请求设置NSPersistentHistoryChangeRequest
的fetchRequest
属性。
构建并运行,并观察控制台。 您将看到所有这些工作的结果。 删除一个火球,您将看不到任何打印到控制台的交易,因为您正在直接过滤掉由用户生成的交易。 但是,如果您随后点击刷新按钮,则会看到出现一个新事务,因为这是批导入添加的新记录。 成功!
那是一个漫长的过程-您最近好吗? 在这些艰难时期,记住您应用程序的核心使命始终是一件好事:拯救人类免受外来入侵。 都值得!
6. Step 6: Merging Important Changes
好的,您已经添加了所有必要的优化,以确保您的视图上下文(view context)
流程仅从最相关的事务中进行更改。 剩下要做的就是将这些更改合并到视图上下文中以更新UI。 这是相对简单的。
将以下方法添加到您的PersistenceController
:
private func mergeChanges(from transactions: [NSPersistentHistoryTransaction]) {
let context = viewContext
// 1
context.perform {
// 2
transactions.forEach { transaction in
// 3
guard let userInfo = transaction.objectIDNotification().userInfo else {
return
}
// 4
NSManagedObjectContext
.mergeChanges(fromRemoteContextSave: userInfo, into: [context])
}
}
}
这是上面代码中发生的事情:
- 1) 您确保使用
perform(_ :)
在视图上下文的队列上进行工作。 - 2) 您遍历传递给此方法的每个事务。
- 3) 每个事务都包含每个更改的所有详细信息,但是您需要以可传递给
mergeChanges(fromRemoteContextSave:into :)
的形式使用它:一个userInfo
字典。objectIDNotification().userInfo
只是您需要的字典。 - 4) 将其传递给
mergeChanges(fromRemoteContextSave:into :)
将使视图上下文与事务更改保持最新。
还记得您之前设置的query generation
吗? mergeChanges(fromRemoteContextSave:into :)
方法的作用之一是更新上下文的query generation
。
剩下的就是调用您的新方法。 在调用print(_ :)
之前,将以下行添加到processRemoteStoreChange(_:)
(如果需要,您也可以删除对print(_ :)
的调用!):
self.mergeChanges(from: transactions)
现在,流程更改方法将过滤事务,并将仅最相关的事务传递给mergeChanges(from :)
方法。
构建并运行!
忘记控制台,签出您的应用程序。 刷新两次,第二次您什么也看不到,因为不需要任何工作。 然后,删除一个火球,然后点击刷新按钮。 您会看到它再次出现!
Adding Derived Attributes
您可以将火球添加到组中,因此最好在组列表中显示火球计数。
派生属性是Core Data
的最新添加,允许您创建一个实体属性,该实体属性是在每次将上下文保存并存储到持久性存储区时从子entity
数据计算得出的。 这使它高效,因为您不必在每次读取时都重新计算它。
您在managed object model
中创建派生属性。 打开FireballWatch.xcdatamodeld
,然后选择FireballGroup entity
。 找到Attributes
部分,然后单击加号按钮以添加新属性。 将其称为fireballCount
并将类型设置为Integer 64
。
在右侧的Data Model inspector
中,选中Derived
复选框,其中将显示Derivation
字段。 在此字段中,键入以下内容:
fireballs.@count
这使用谓词聚合函数@count
并作用于现有的fireballs
关系以返回该组的child entities
有多少个火球的计数。
记住要保存您的managed object model
。
注意:从Xcode 12开始,派生属性仅限于一些特定的用例。 您可以find out what's possible in the Apple documentation。
剩下要做的就是显示计数。
打开View group
中的FireballGroupList.swift
,找到以下行:
Text("\(group.name ?? "Untitled")")
替换成下面的:
HStack {
Text("\(group.name ?? "Untitled")")
Spacer()
Image(systemName: "sun.max.fill")
Text("\(group.fireballCount)")
}
这只是向每行添加一个图标和火球计数。 构建并运行以查看其显示方式:
Perfect!
如果您正在寻找挑战,请尝试添加代码以在处理完不必要的交易记录后将其删除,以免历史记录无限期地增长。 有一个方便的工作方法:NSPersistentHistoryChangeRequest.deleteHistoryBefore(_ :)
。
如果您想进一步了解Core Data
,建议您:
- Making Apps with Core Data from WWDC2019
- Using Core Data With CloudKit from WWDC 2019
- Core Data: Sundries and maxims from WWDC 2020
后记
本篇主要讲述了基于批插入和存储历史等高效CoreData使用示例,感兴趣的给个赞或者关注~~~