版本記錄
版本號 | 時間 |
---|---|
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使用示例,感興趣的給個贊或者關注~~~