版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2020.12.10 星期四 |
前言
數(shù)據(jù)的持久化存儲是移動端不可避免的一個問題蛋褥,很多時候的業(yè)務(wù)邏輯都需要我們進行本地化存儲解決和完成耻蛇,我們可以采用很多持久化存儲方案冕末,比如說
plist
文件(屬性列表)憋肖、preference
(偏好設(shè)置)朝群、NSKeyedArchiver
(歸檔)姜盈、SQLite 3
、CoreData
猪腕,這里基本上我們都用過冗澈。這幾種方案各有優(yōu)缺點,其中陋葡,CoreData是蘋果極力推薦我們使用的一種方式亚亲,我已經(jīng)將它分離出去一個專題進行說明講解。這個專題主要就是針對另外幾種數(shù)據(jù)持久化存儲方案而設(shè)立脖岛。
1. 數(shù)據(jù)持久化方案解析(一) —— 一個簡單的基于SQLite持久化方案示例(一)
2. 數(shù)據(jù)持久化方案解析(二) —— 一個簡單的基于SQLite持久化方案示例(二)
3. 數(shù)據(jù)持久化方案解析(三) —— 基于NSCoding的持久化存儲(一)
4. 數(shù)據(jù)持久化方案解析(四) —— 基于NSCoding的持久化存儲(二)
5. 數(shù)據(jù)持久化方案解析(五) —— 基于Realm的持久化存儲(一)
6. 數(shù)據(jù)持久化方案解析(六) —— 基于Realm的持久化存儲(二)
7. 數(shù)據(jù)持久化方案解析(七) —— 基于Realm的持久化存儲(三)
8. 數(shù)據(jù)持久化方案解析(八) —— UIDocument的數(shù)據(jù)存儲(一)
9. 數(shù)據(jù)持久化方案解析(九) —— UIDocument的數(shù)據(jù)存儲(二)
10. 數(shù)據(jù)持久化方案解析(十) —— UIDocument的數(shù)據(jù)存儲(三)
11. 數(shù)據(jù)持久化方案解析(十一) —— 基于Core Data 和 SwiftUI的數(shù)據(jù)存儲示例(一)
12. 數(shù)據(jù)持久化方案解析(十二) —— 基于Core Data 和 SwiftUI的數(shù)據(jù)存儲示例(二)
13. 數(shù)據(jù)持久化方案解析(十三) —— 基于Unit Testing的Core Data測試(一)
14. 數(shù)據(jù)持久化方案解析(十四) —— 基于Unit Testing的Core Data測試(二)
15. 數(shù)據(jù)持久化方案解析(十五) —— 基于Realm和SwiftUI的數(shù)據(jù)持久化簡單示例(一)
16. 數(shù)據(jù)持久化方案解析(十六) —— 基于Realm和SwiftUI的數(shù)據(jù)持久化簡單示例(二)
17. 數(shù)據(jù)持久化方案解析(十七) —— 基于NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例(一)
18. 數(shù)據(jù)持久化方案解析(十八) —— 基于NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例(二)
開始
首先看下主要內(nèi)容:
在本教程中朵栖,您將學習如何借助批處理插入,持久性歷史記錄和派生屬性的有效
Core Data
使用來改進iOS應(yīng)用柴梆。內(nèi)容來自翻譯。
下面看下寫作環(huán)境:
Swift 5, iOS 14, Xcode 12
接著就是主要內(nèi)容了终惑。
Core Data
是已存在很長時間的古老的Apple
框架之一绍在。自從iOS 10
中發(fā)布NSPersistentContainer
以來,蘋果公司就向Core Data
表示了極大的熱愛。最新添加的Core Data
進一步提升了其競爭力〕ザ桑現(xiàn)在有批量插入請求臼寄,持久性歷史記錄和派生屬性,這些絕對可以使Core Data
的使用效率更高溜宽。
在本教程中吉拳,您將通過提高數(shù)據(jù)存儲效率來改進應(yīng)用程序。您將學習如何:
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應(yīng)用程序和編寫Swift的經(jīng)驗留攒。您應(yīng)該已經(jīng)使用過
Core Data
,并對其概念感到滿意嫉嘀。如果您想學習基礎(chǔ)知識炼邀,可以先嘗試Core Data with SwiftUI tutorial。
Fireballs
剪侮!他們無處不在拭宁!有人在注意嗎?Fireballs
可能是外星人入侵的最初跡象瓣俯,也可能是即將來臨的大決戰(zhàn)的預(yù)兆杰标。有人必須保持警惕。這是你的任務(wù)彩匕。您已經(jīng)制作了一個應(yīng)用程序腔剂,可以從NASA Jet Propulsion Laboratory (JPL)
下載火球瞄準點,以便將它們分組并報告可疑的火球活動推掸。
打開啟動項目桶蝎。 看你到目前為止有什么。
Exploring Fireball Watch
構(gòu)建并運行該應(yīng)用程序谅畅,以便您可以了解其工作方式登渣。 該應(yīng)用程序從JPL
下載最新的火球數(shù)據(jù),為每個火球瞄準創(chuàng)建記錄并將其存儲在Core Data stack
中毡泻。 您還可以創(chuàng)建組并將火球添加到組中以進行報告胜茧。
啟動時,列表將為空仇味,因此請點擊Fireballs
列表右上角的刷新按鈕呻顽。 很快,該列表就會填滿丹墨。 您可以再次點擊以查看它沒有為相同數(shù)據(jù)添加重復(fù)記錄廊遍。 如果您在某些火球單元上向左滑動并刪除了一些,然后再次點擊刷新贩挣,則會看到下載數(shù)據(jù)后重新創(chuàng)建的那些fireballs
喉前。
如果點擊Groups
選項卡没酣,則可以添加一個組。 進行一些分組卵迂,然后返回Fireballs
選項卡裕便,然后在列表中點擊一個火球。 然后见咒,點擊右上角的in-tray
按鈕以選擇一個或多個包含該火球的組偿衰。 當您點擊Groups
標簽中列出的組列表時,它將向您顯示那個組中所有火球的地圖改览。
注意:您可以在此處閱讀有關(guān)JPLfireball API here的信息下翎。
Examining the Core Data Stack
現(xiàn)在,看看應(yīng)用程序的Core Data stack
是如何設(shè)置的恃疯。
打開Persistence.swift
漏设。 您會看到一個名為PersistenceController
的類。 此類處理您的所有Core Data
設(shè)置和數(shù)據(jù)導(dǎo)入今妄。 它使用NSPersistentContainer
創(chuàng)建一個標準的SQLite
存儲郑口,或者創(chuàng)建一個用于SwiftUI
預(yù)覽的內(nèi)存存儲。
persistent container
的viewContext
是應(yīng)用程序用于獲取請求(生成列表數(shù)據(jù))的managed object context
盾鳞。 這是典型的設(shè)置犬性。 您的模型中有兩個實體(entities)
:Fireball
和FireballGroup
。
PersistenceController
具有fetchFireballs()
腾仅,可下載火球數(shù)據(jù)并調(diào)用私有importFetchedFireballs(_ :)
以將所得的FireballData struct
數(shù)組導(dǎo)入為Fireball
的managed objects
乒裆。 它使用持久性容器的performBackgroundTask(_ :)
作為后臺任務(wù)來執(zhí)行此操作。
importFetchedFireballs(_ :)
循環(huán)遍歷FireballData
數(shù)組推励,創(chuàng)建一個managed object
并保存managed object context
鹤耍。 由于永久性容器的viewContext
將automaticallyMergesChangesFromParent
設(shè)置為true
,因此在應(yīng)用程序保存所有對象時验辞,這可能會使UI
停滯稿黄。 這是一個會使應(yīng)用感覺很笨拙的問題,是您第一次改進的目標跌造。
Making a Batch Insert Request
報告的火球列表只會越來越大杆怕,如果突然出現(xiàn)火球群怎么辦? 火球群可能表明可能有外星人著陸點壳贪,預(yù)示著新的入侵嘗試陵珍!
您希望初始下載盡可能靈活。 您的應(yīng)用程序需要快速使您掌握最新數(shù)據(jù)违施。 任何暫停互纯,延遲或掛起都是不可接受的。
批量插入可助您一臂之力磕蒲! 批處理插入請求是一種特殊的持久性存儲請求伟姐,它允許您將大量數(shù)據(jù)直接導(dǎo)入到持久性存儲中收苏。 您需要一個方法來為此操作創(chuàng)建批量插入請求亿卤。 打開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
對象數(shù)組愤兵,并創(chuàng)建一個NSBatchInsertRequest
來插入所有對象。就是這樣:
- 1) 您首先創(chuàng)建局部變量以保存當前循環(huán)索引和總火球計數(shù)排吴。
- 2) 使用
NSBatchInsertRequest(entity:managedObjectHandler :)
創(chuàng)建批處理插入請求秆乳。此方法要求您要執(zhí)行的每個插入都執(zhí)行一個NSEntity
和一個閉包 —— 每個火球一個。如果是最后一次插入钻哩,則閉包必須返回true
屹堰。 - 3) 在閉包內(nèi)部,您首先要檢查是否已到達火球數(shù)組的末尾街氢,如果返回
true
扯键,則完成請求。 - 4) 在這里插入新數(shù)據(jù)珊肃。使用
NSManagedObject
實例調(diào)用該閉包荣刑。這是一個新對象,并檢查其類型為Fireball
(始終為伦乔,但應(yīng)始終安全)厉亏,然后設(shè)置對象的屬性以匹配獲取的Fireball
數(shù)據(jù)。 - 5) 最后烈和,您增加索引并返回
false
爱只,表示插入請求應(yīng)再次調(diào)用閉包。
注意:在
iOS 13
中招刹,當NSBatchInsertRequest
首次發(fā)布時恬试,只有一個初始化程序采用了表示所有要插入數(shù)據(jù)的字典數(shù)組。在iOS 14
中疯暑,添加了四個新變體训柴,每個變體使用閉包樣式的初始化程序以及managed object
或字典。有關(guān)更多信息缰儿,請參閱 See the Apple documentation for more information畦粮。
Batch Inserting Fireballs
這樣就完成了請求創(chuàng)建。 現(xiàn)在乖阵,您如何使用它宣赔? 將以下方法添加到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) 首先,請檢查是否有實際的工作要做瞪浸,以確保數(shù)組不為空儒将。
- 2) 然后要求
PersistentContainer
使用performBackgroundTask(_ :)
執(zhí)行后臺任務(wù)。 - 3) 創(chuàng)建批處理插入請求对蒲,然后執(zhí)行它钩蚊,捕獲可能引發(fā)的任何錯誤贡翘。 批處理請求通過一次事務(wù)將所有數(shù)據(jù)插入持久性存儲
(persistent store)
中。 由于您的Core Data model
已定義了唯一約束砰逻,因此它將僅創(chuàng)建不存在的新記錄鸣驱,并在需要時更新現(xiàn)有記錄。
最后一項更改:轉(zhuǎn)到fetchFireballs()
蝠咆,而不是調(diào)用self踊东?.importFetchedFireballs($ 0)
,將其更改為:
self?.batchInsertFireballs($0)
您也可以注釋或刪除importFetchedFireballs(_ :)
刚操,因為不再需要它闸翅。
注意:如果您想知道,批處理插入請求不能設(shè)置
Core Data entity relationship
菊霜,但是它們將保持現(xiàn)有關(guān)系不變坚冀。 有關(guān)更多信息,請參見使用WWDC2019中的 Making Apps with Core Data鉴逞。
剩下要做的就是構(gòu)建并運行记某!
但是您可能會注意到有些問題。 如果刪除火球华蜒,然后再次點擊刷新按鈕辙纬,則列表不會更新。 那是因為批處理插入請求將數(shù)據(jù)插入到持久性存儲(persistent store)
中叭喜,但是視圖上下文(view context)
沒有更新贺拣,因此它不知道任何更改。 您可以通過重啟應(yīng)用來確認這一點捂蕴,然后您將看到所有新數(shù)據(jù)現(xiàn)在都顯示在列表中譬涡。
以前,您是在后臺隊列上下文(background queue context)
中創(chuàng)建對象并保存上下文啥辨,這會將更改推送到持久性存儲協(xié)調(diào)器(persistent store coordinator)
涡匀。保存后臺上下文后,它已從持久性存儲協(xié)調(diào)器自動更新溉知,因為您已在視圖上下文中將automaticallyMergeChangesFromParent
設(shè)置為true
陨瘩。
持久性存儲(persistent store)
請求的部分效率是它們直接在持久性存儲上運行,并且避免將數(shù)據(jù)加載到內(nèi)存中或生成上下文保存通知级乍。因此舌劳,在應(yīng)用程序運行時,您將需要一種新的策略來更新視圖上下文玫荣。
Enabling Notifications
當然甚淡,在后臺更新存儲并非不常見。例如捅厂,您可能具有一個用于擴展持久性存儲(persistent store)
的應(yīng)用程序擴展贯卦,或者您的應(yīng)用程序支持iCloud
资柔,并且您的應(yīng)用程序的存儲更新來自其他設(shè)備的更改。令人高興的是撵割,iOS
提供了一個通知– NSPersistentStoreRemoteChange
—每當存儲更新發(fā)生時贿堰,該通知就會發(fā)送。
再次打開Persistence.swift
并跳轉(zhuǎn)到init(inMemory :)
睁枕。在PersistentContainer
上調(diào)用loadPersistentStores(completionHandler :)
的行之前斋荞,添加以下行:
persistentStoreDescription?.setOption(
true as NSNumber,
forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
添加這一行會導(dǎo)致您的存儲在每次更新時生成通知励两。
現(xiàn)在柱恤,您需要以某種方式使用此通知镰绎。 首先赏廓,向PersistenceController
添加一個空方法佑淀,該方法將作為所有更新處理邏輯的占位符:
func processRemoteStoreChange(_ notification: Notification) {
print(notification)
}
您的占位符方法只是將通知打印到Xcode
控制臺嘴高。
接下來浸策,通過將其添加到init(inMemory :)
的末尾捐晶,使用NotificationCenter
發(fā)布者訂閱通知:
NotificationCenter.default
.publisher(for: .NSPersistentStoreRemoteChange)
.sink {
self.processRemoteStoreChange($0)
}
.store(in: &subscriptions)
每當您的應(yīng)用收到通知時菲语,它將調(diào)用您的新processRemoteStoreChange(_ :)
。
構(gòu)建并運行惑灵,您將看到Xcode控制臺中有關(guān)每個更新的通知山上。 嘗試刷新火球列表,添加組英支,刪除火球等佩憾。 存儲的所有更新將生成一條通知。
那么干花,此通知對您有何幫助妄帘? 如果您想保持簡單,則只要收到通知就可以刷新視圖上下文(view context)
池凄。 但是抡驼,有一種更智能,更高效的方法肿仑。 這就是您進入持久性歷史記錄跟蹤(persistent history tracking)
的原因致盟。
Enabling Persistent History Tracking
如果啟用持久性歷史記錄跟蹤(persistent history tracking)
,則Core Data
會保留持久性存儲中發(fā)生的所有事務(wù)的事務(wù)處理歷史記錄尤慰。 這使您可以查詢歷史記錄馏锡,以準確查看更新或創(chuàng)建了哪些對象,并將僅那些更改合并到視圖上下文中割择。
要啟用持久性歷史記錄跟蹤眷篇,請將此行添加到init(inMemory :)
中,緊接在PersistentContainer
上調(diào)用loadPersistentStores(completionHandler :)
的行之前:
persistentStoreDescription?.setOption(
true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
就這些荔泳! 現(xiàn)在蕉饼,該應(yīng)用程序會將每次更改的交易歷史記錄保存到您的持久性存儲中虐杯,您可以通過提取請求查詢該歷史記錄。
Making a History Request
現(xiàn)在昧港,當您的應(yīng)用收到存儲的遠程更改通知時擎椰,它可以查詢存儲的歷史記錄以發(fā)現(xiàn)更改內(nèi)容。 由于存儲更新可能來自多個來源创肥,因此您將需要使用串行隊列來執(zhí)行工作达舒。 這樣,如果同時發(fā)生多組變更叹侄,您將避免沖突或競爭條件巩搏。
在init(inMemory :)
之前將隊列屬性添加到您的類中
private lazy var historyRequestQueue = DispatchQueue(label: "history")
現(xiàn)在,您可以返回到processRemoteStoreChange(_ :)
趾代,刪除print()
語句并添加以下將執(zhí)行歷史記錄請求的代碼:
// 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
}
}
}
這是上面代碼中發(fā)生的事情:
- 1) 您可以將此代碼作為歷史隊列中的一個
block
運行贯底,以串行方式處理每個通知。 - 2) 要執(zhí)行此工作撒强,請創(chuàng)建一個新的后臺上下文
(background context)
禽捆,并使用performAndWait(_ :)
在該新上下文中運行一些代碼。 - 3) 您可以使用
NSPersistentHistoryChangeRequest.fetchHistory(after :)
返回NSPersistentHistoryChangeRequest
飘哨,它是NSPersistentStoreRequest
的子類胚想,可以執(zhí)行以獲取歷史交易數(shù)據(jù)。 - 4) 您執(zhí)行請求芽隆,并將結(jié)果強制進入
NSPersistentHistoryTransaction
對象數(shù)組浊服。歷史記錄請求的默認結(jié)果類型就是這樣的對象數(shù)組。這些對象還包含NSPersistentHistoryChange
對象摆马,它們是與返回的事務(wù)相關(guān)的所有更改臼闻。 - 5) 您將在此處處理更改。現(xiàn)在囤采,您只需將返回的事務(wù)打印到控制臺述呐。
構(gòu)建并運行并執(zhí)行常規(guī)的測試:點按“刷新”按鈕,刪除一些火球蕉毯,然后再次刷新等等乓搬。您會發(fā)現(xiàn)通知已到達,并且一系列事務(wù)對象已打印到Xcode控制臺代虾。
Revealing a Conundrum: Big Notifications
這揭示了一個難題进肯,如果您已經(jīng)注意到它,那就做得好棉磨!
永久存儲的任何更改都會觸發(fā)通知江掩,即使您的用戶從用戶交互中添加或刪除managed object
也是如此。 不僅如此:請注意,您的歷史記錄提取請求還會返回事務(wù)日志開頭的所有更改环形。
您的通知也太大太多啦策泣!
您的意圖是避免對視圖上下文(view context)
進行任何不必要的工作,控制何時刷新視圖上下文抬吟。 完全沒有問題萨咕,您已經(jīng)覆蓋了它。 為了使整個過程清晰明了火本,您將通過幾個易于遵循的步驟來做到這一點危队。
1. Step 1: Setting a Query Generation
第一步 —— (邁向控制視圖上下文(view context)
的一個小步驟)是設(shè)置查詢生成(query generation)
。 在Persistence.swift
中钙畔,將其添加到NotificationCenter
發(fā)布者之前的init(inMemory :)
中:
if !inMemory {
do {
try viewContext.setQueryGenerationFrom(.current)
} catch {
// log any errors
}
}
您將通過調(diào)用setQueryGenerationFrom(_ :)
將視圖上下文固定到持久性存儲(persistent store)
中的最新事務(wù)茫陆。 但是,由于設(shè)置query generation
僅與SQLite
存儲兼容刃鳄,因此僅當inMemory
為false
時才這樣做盅弛。
2. Step 2: Saving the History Token
您的歷史記錄請求使用日期來限制結(jié)果,但是有更好的方法叔锐。
NSPersistentHistoryToken
是一個不透明的對象,用于標記persistent store's transaction history
中的位置见秽。 從歷史記錄請求返回的每個交易對象都有一個token
愉烙。 您可以存儲它,以便在查詢持久性歷史記錄時知道從哪里開始解取。
您將需要一個屬性步责,用于存儲在應(yīng)用程序運行時使用的token
,一種將token
另存為磁盤上文件的方法禀苦,以及從已保存的文件加載token
的方法蔓肯。
在historyRequestQueue
之后,將以下屬性添加到PersistenceController
:
private var lastHistoryToken: NSPersistentHistoryToken?
這樣會將token
存儲在內(nèi)存中振乏,當然蔗包,您需要一個位置將其存儲在磁盤上。 接下來慧邮,添加此屬性:
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
將嘗試創(chuàng)建存儲目錄。
接下來误澳,添加一種將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
數(shù)據(jù)存檔到磁盤上的文件中耻矮,并更新lastHistoryToken
。
返回到processRemoteStoreChange(_ :)
并找到以下代碼:
let request = NSPersistentHistoryChangeRequest
.fetchHistory(after: .distantPast)
使用下面進行替換:
let request = NSPersistentHistoryChangeRequest
.fetchHistory(after: self.lastHistoryToken)
從token
的上次更新以來忆谓,這僅從請求整個歷史變?yōu)檎埱髿v史裆装。
接下來,您可以從返回的事務(wù)數(shù)組中的最后一個事務(wù)中獲取history token
并進行存儲。 在print()
語句下哨免,添加:
if let newToken = transactions.last?.token {
self.storeHistoryToken(newToken)
}
構(gòu)建并運行茎活,觀察Xcode控制臺,然后點擊“刷新”按鈕铁瞒。 第一次您應(yīng)該從頭開始查看所有交易妙色。 第二次您應(yīng)該看到的更少了,也許沒有慧耍。 既然您已經(jīng)下載了所有火球并存儲了最后的交易歷史記錄token
身辨,那么可能沒有較新的交易記錄了。
除非有新的火球發(fā)現(xiàn)芍碧!
3. Step 3: Loading the History Token
當您的應(yīng)用啟動時煌珊,您還希望它加載最后保存的歷史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
數(shù)據(jù)存在泌豆,此方法將取消存檔定庵,并設(shè)置lastHistoryToken
屬性。
通過將其添加到init(inMemory :)
的末尾來調(diào)用此方法:
loadHistoryToken()
構(gòu)建并運行并再次查看控制臺踪危。 不應(yīng)有新交易蔬浙。 這樣,您的應(yīng)用程序便可以立即查詢歷史記錄日志贞远!
4. Step 4: Setting a Transaction Author
您可以進一步完善歷史記錄處理畴博。 每個Core Data managed object context
都可以設(shè)置transaction author。transaction author
存儲在歷史記錄中蓝仲,并成為一種識別每個變更來源的方法俱病。 通過這種方式,您可以直接從后臺導(dǎo)入import
過程所做的更改中分辨出用戶所做的更改袱结。
首先亮隙,在PersistenceController
的頂部,添加以下靜態(tài)屬性:
private static let authorName = "FireballWatch"
private static let remoteDataImportAuthorName = "Fireball Data Import"
這是您將用作作者名稱的兩個靜態(tài)字符串垢夹。
注意:如果要記錄交易記錄溢吻,請務(wù)必有一位上下文作者,這一點很重要棚饵。
接下來煤裙,在設(shè)置viewContext.automaticallyMergesChangesFromParent
的調(diào)用的正下方添加以下內(nèi)容到init(inMemory :)
行中:
viewContext.transactionAuthor = PersistenceController.authorName
這將使用您剛創(chuàng)建的靜態(tài)屬性設(shè)置view context
的transaction author
。
接下來噪漾,向下滾動至batchInsertFireballs(_ :)
硼砰,然后在傳遞給performBackgroundTask(_ :)
的閉包內(nèi),在開頭添加以下行:
context.transactionAuthor = PersistenceController.remoteDataImportAuthorName
這會將用于將數(shù)據(jù)導(dǎo)入到其他靜態(tài)屬性的后臺上下文的transaction author
設(shè)置欣硼。 因此题翰,現(xiàn)在根據(jù)對上下文的更改記錄的歷史記錄將具有可識別的來源,而且重要的是,它不同于用于UI更新的transaction author
豹障,例如通過滑動行進行刪除冯事。
5. Step 5: Creating a History Request Predicate
要過濾掉由用戶引起的任何交易,您需要添加帶有謂詞的提取請求血公。
找到processRemoteStoreChange(_ :)
并在執(zhí)行do
之前添加以下內(nèi)容:
if let historyFetchRequest = NSPersistentHistoryTransaction.fetchRequest {
historyFetchRequest.predicate =
NSPredicate(format: "%K != %@", "author", PersistenceController.authorName)
request.fetchRequest = historyFetchRequest
}
首先昵仅,使用類屬性NSPersistentHistoryTransaction.fetchRequest
創(chuàng)建一個NSFetchRequest
并設(shè)置其謂詞。 如果transaction author
不是您創(chuàng)建的用于識別用戶交易的字符串累魔,則謂詞測試將返回true
摔笤。 然后,使用此謂詞獲取請求設(shè)置NSPersistentHistoryChangeRequest
的fetchRequest
屬性垦写。
構(gòu)建并運行吕世,并觀察控制臺。 您將看到所有這些工作的結(jié)果梯投。 刪除一個火球命辖,您將看不到任何打印到控制臺的交易,因為您正在直接過濾掉由用戶生成的交易分蓖。 但是尔艇,如果您隨后點擊刷新按鈕,則會看到出現(xiàn)一個新事務(wù)么鹤,因為這是批導(dǎo)入添加的新記錄漓帚。 成功!
那是一個漫長的過程-您最近好嗎午磁? 在這些艱難時期,記住您應(yīng)用程序的核心使命始終是一件好事:拯救人類免受外來入侵毡们。 都值得迅皇!
6. Step 6: Merging Important Changes
好的,您已經(jīng)添加了所有必要的優(yōu)化衙熔,以確保您的視圖上下文(view context)
流程僅從最相關(guān)的事務(wù)中進行更改登颓。 剩下要做的就是將這些更改合并到視圖上下文中以更新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])
}
}
}
這是上面代碼中發(fā)生的事情:
- 1) 您確保使用
perform(_ :)
在視圖上下文的隊列上進行工作框咙。 - 2) 您遍歷傳遞給此方法的每個事務(wù)。
- 3) 每個事務(wù)都包含每個更改的所有詳細信息痢甘,但是您需要以可傳遞給
mergeChanges(fromRemoteContextSave:into :)
的形式使用它:一個userInfo
字典喇嘱。objectIDNotification().userInfo
只是您需要的字典。 - 4) 將其傳遞給
mergeChanges(fromRemoteContextSave:into :)
將使視圖上下文與事務(wù)更改保持最新塞栅。
還記得您之前設(shè)置的query generation
嗎者铜? mergeChanges(fromRemoteContextSave:into :)
方法的作用之一是更新上下文的query generation
。
剩下的就是調(diào)用您的新方法。 在調(diào)用print(_ :)
之前作烟,將以下行添加到processRemoteStoreChange(_:)
(如果需要愉粤,您也可以刪除對print(_ :)
的調(diào)用!):
self.mergeChanges(from: transactions)
現(xiàn)在拿撩,流程更改方法將過濾事務(wù)衣厘,并將僅最相關(guān)的事務(wù)傳遞給mergeChanges(from :)
方法。
構(gòu)建并運行压恒!
忘記控制臺影暴,簽出您的應(yīng)用程序。 刷新兩次涎显,第二次您什么也看不到坤检,因為不需要任何工作。 然后期吓,刪除一個火球早歇,然后點擊刷新按鈕。 您會看到它再次出現(xiàn)讨勤!
Adding Derived Attributes
您可以將火球添加到組中箭跳,因此最好在組列表中顯示火球計數(shù)。
派生屬性是Core Data
的最新添加潭千,允許您創(chuàng)建一個實體屬性谱姓,該實體屬性是在每次將上下文保存并存儲到持久性存儲區(qū)時從子entity
數(shù)據(jù)計算得出的。 這使它高效刨晴,因為您不必在每次讀取時都重新計算它屉来。
您在managed object model
中創(chuàng)建派生屬性。 打開FireballWatch.xcdatamodeld
狈癞,然后選擇FireballGroup entity
茄靠。 找到Attributes
部分,然后單擊加號按鈕以添加新屬性蝶桶。 將其稱為fireballCount
并將類型設(shè)置為Integer 64
慨绳。
在右側(cè)的Data Model inspector
中,選中Derived
復(fù)選框真竖,其中將顯示Derivation
字段脐雪。 在此字段中,鍵入以下內(nèi)容:
fireballs.@count
這使用謂詞聚合函數(shù)@count
并作用于現(xiàn)有的fireballs
關(guān)系以返回該組的child entities
有多少個火球的計數(shù)恢共。
記住要保存您的managed object model
战秋。
注意:從Xcode 12開始,派生屬性僅限于一些特定的用例旁振。 您可以find out what's possible in the Apple documentation获询。
剩下要做的就是顯示計數(shù)涨岁。
打開View group
中的FireballGroupList.swift
,找到以下行:
Text("\(group.name ?? "Untitled")")
替換成下面的:
HStack {
Text("\(group.name ?? "Untitled")")
Spacer()
Image(systemName: "sun.max.fill")
Text("\(group.fireballCount)")
}
這只是向每行添加一個圖標和火球計數(shù)吉嚣。 構(gòu)建并運行以查看其顯示方式:
Perfect!
如果您正在尋找挑戰(zhàn)梢薪,請嘗試添加代碼以在處理完不必要的交易記錄后將其刪除,以免歷史記錄無限期地增長尝哆。 有一個方便的工作方法: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使用示例秋泄,感興趣的給個贊或者關(guān)注~~~