前言
雖然在 iOS 上開發了幾年的時間,但一直到最近才開始使用 CoreData;之前在第一份工作的專案之中是使用 FMDB 來處理資料存取,而後續則是用了 Realm。
選擇的原因分別是當時 FMDB 的速度較其餘兩者快速,而後來看上 Realm 的跨平台特色,不過近期開發的感想是能以原生為主的話,就儘量降低對於第三方套件的依賴性。
這篇文章會記錄些什麼
其實這篇文章並不會從頭到尾寫下教學,而把重點放在一些我踩到的雷上,像是⋯⋯
記得要附上 sqlite 的路徑
原先我的 persistentContainer 的產生方式如下
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "OfflineWallet")
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { _, error in
if let error = error {
fatalError("Unresolved error \(error), \(String(describing: error._userInfo))")
}
}
return container
}()
在模擬器上存取了幾次,每次都有 save 且第二次進入畫面的時候,都可以 fetch 得到資料,但是只要重開 App 就會從頭來過⋯⋯
也就是說其實都只是像是存在 NSManagedObjectContext 上,而並沒有實際地轉成 sqlite。
補上指定的 URL 即可解決。
...
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let sqliteURL = documentsDirectoryURL?.appendingPathComponent("OfflineWallet.sqlite")
description.url = sqliteURL
...
performBackgroundTask v.s newBackgroundContext
要實作 backgroundTask 有兩種作法
NSPersistentContainer.performBackgroundTask
persistentContainer.performBackgroundTask { context in
//do something in background thread
}
NSPersistentContainer 會建立一個 context 在這個 closure 裡頭使用,而重點是當這條 thread 結束之後,這個 context 所管理的物件(NSManagedObjectModel)的所有變數會釋放掉,也就是都會變成 nil。
適合用於取得資料並轉型成其他 class / structure 的時候使用。
newBackgroundContext
而如果你必須在後續的程式之中繼續使用 context 所產生的 NSManagedObjectModel 時,你就得要保存其 context;像是建立一個 backgroundContext 並存下來使用:
lazy var backgroundContext = persistentContainer.newBackgroundContext()
//you should use this context to do something what you want
backgroundContext.perform {
//do something like before
}
此時由於這個 context 並沒有被釋放掉,所以其 NSManagedObjectModel 的所有變數便也會持有著;而依然是在其他 thread 上進行,並不會佔據 main thread。
待續
分頁讀取
筆記一下 NSFetchRequest 有提供 fetchLimit 以及 fetchOffset 可以用來做分頁讀取的功能。