iOS 10 中的 NSPersistentContainer
Xcode 8 已經(jīng)面世了京闰,如果你還沒有嘗試過這個測試版本经伙,你將會發(fā)現(xiàn)各種新東西贰健。這里有 Swift 3 [主要的更新]辑鲤,有新的框架呻粹,比如 [SiriKit]和一些對現(xiàn)存特性的增強改進,比如 [notifications]昼扛。 我們也接收以 NSPersistentContainer
形式的簡化版的 Core Data stack
寸齐,它為我們做了大部分的準備工作。它值得我們?nèi)L試么?讓我們開始深入挖掘這些新特性吧访忿。
iOS 10
之前的 Core Data stack
多年來,在嘗試了很多種 Core Data stack
之后斯稳,我們選定了兩個簡單的 stack
海铆,融合成一個使用。讓我們仔細看一下這些關(guān)鍵組件并開始連接使用他們挣惰。完整版本的 Github
鏈接在引用中能找到卧斟。代碼已經(jīng)適配到 Swift 3
和 Xcode 8
。
final class CoreDataStack {
static let sharedStack = CoreDataStack()
var errorHandler: (Error) -> Void = {_ in }
private init() {
#1
NotificationCenter.default.addObserver(self,
selector: #selector(CoreDataStack.mainContextChanged(notification:)),
name: .NSManagedObjectContextDidSave,
object: self.managedObjectContext)
NotificationCenter.default.addObserver(self,
selector: #selector(CoreDataStack.bgContextChanged(notification:)),
name: .NSManagedObjectContextDidSave,
object: self.backgroundManagedObjectContext)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#2
lazy var applicationDocumentsDirectory: NSURL = {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return urls[urls.count-1] as NSURL
}()
#3
lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = Bundle.main.url(forResource: "DataModel", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
#4
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.appendingPathComponent("DataModel.sqlite")
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType,
configurationName: nil,
at: url,
options: [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true])
} catch {
// Report any error we got.
NSLog("CoreData error \(error), \(error._userInfo)")
self.errorHandler(error)
}
return coordinator
}()
#5
lazy var backgroundManagedObjectContext: NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
var privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedObjectContext.persistentStoreCoordinator = coordinator
return privateManagedObjectContext
}()
#6
lazy var managedObjectContext: NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
var mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
mainManagedObjectContext.persistentStoreCoordinator = coordinator
return mainManagedObjectContext
}()
#7
@objc func mainContextChanged(notification: NSNotification) {
backgroundManagedObjectContext.perform { [unowned self] in
self.backgroundManagedObjectContext.mergeChanges(fromContextDidSave: notification as Notification)
}
}
@objc func bgContextChanged(notification: NSNotification) {
managedObjectContext.perform{ [unowned self] in
self.managedObjectContext.mergeChanges(fromContextDidSave: notification as Notification)
}
}
}
上面是啥憎茂?且容我慢慢道來珍语。
#1
在初始化的時候,我們訂閱了從主線程和后臺線程 NSMagedObjectContext
發(fā)送來的通知竖幔。
#2
獲取文檔路徑 NSURL的 getter板乙。NSPersistentStoreCoordinator
使用它在給定的位置創(chuàng)建 NSPersistentStore。
#3
和文件目錄相似拳氢,他獲得 NSManagedObjectModel 的 getter
方法募逞,用它來初始化有我們模型的 NSPersistentStoreCoordinator。
#4
這就是這些神奇的代碼干的事情馋评。首先放接,我們創(chuàng)建有模型的 NSPersistentStoreCoordinator。之后留特,我們獲取我們文檔目錄的 url纠脾。最后,我們在這些文檔目錄內(nèi)為某些類型的 NSPersistentStoreCoordinator
增加一個持久化的存儲蜕青。
#5
我們在一個私有隊列里創(chuàng)建一個’后臺’ NSManagedObjectContext
并且把它綁定到 NSPersistentStoreCoordinator
苟蹈。這個 context被用于執(zhí)行同步和寫操作。
#6
我們在主隊列中創(chuàng)建一個’視圖’ NSManagedObjectContext
并且把它綁定到我們的 NSPersistentStoreCoordinator
右核。這個 context
被用于獲取顯示在 UI
上的數(shù)據(jù)汉操。
#7
這個 stack使用了穩(wěn)定、成熟的融合過的 contexts蒙兰,它被保存的 notifications 驅(qū)動磷瘤。在這些方法中,我們執(zhí)行這個融合搜变。NSPersistentContainer
簡介
iOS 10 給我們提供了 NSPersistentContainer采缚。它意圖簡化代碼并且為我們解決負擔。它能做到么挠他?讓我展示給你我們基于 NSPersistentContainer
重建 CoreData stack 扳抽。
一個完整的例子:
final class CoreDataStack {
static let shared = CoreDataStack()
var errorHandler: (Error) -> Void = {_ in }
#1
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores(completionHandler: { [weak self](storeDescription, error) in
if let error = error {
NSLog("CoreData error \(error), \(error._userInfo)")
self?.errorHandler(error)
}
})
return container
}()
#2
lazy var viewContext: NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
#3
// Optional
lazy var backgroundContext: NSManagedObjectContext = {
return self.persistentContainer.newBackgroundContext()
}()
#4
func performForegroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
self.viewContext.perform {
block(self.viewContext)
}
}
#5
func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
self.persistentContainer.performBackgroundTask(block)
}
}
實際上這個更簡短。但是之前版本的代碼發(fā)生了什么?
簡單的答案是贸呢,NSPersistentContainer
已可以為我們代勞镰烧。對于一個博客文章的解釋,這肯定不夠 ?? 楞陷。還是容我慢慢道來怔鳖。
#1
這里,我們能看到 NSPersistentContainer的能力固蛾。它完成了之前 stack
內(nèi)#2, #3, #4, #5, #6 的工作结执,并一定程度上把我們從 #1 和 #7 中的工作中解放出來。怎么做到的艾凯?
首先献幔,它通過一個名字來初始化,這個名字被用于在文檔目錄中查找一個模型并且用相同的名字創(chuàng)建一個存儲器趾诗。這是一個快捷初始器蜡感。你也可以使用完整的版本,手動地傳遞你的模型恃泪。
public init(name:String,managedObjectModel model:NSManagedObjectModel)
之后铸敏,在調(diào)用 loadPersistentStores
方法之前,你還有時間來進一步配置你的容器悟泵,例如杈笔,使用 NSPersistentStoreDescription
。我們使用一個默認的 SQLite數(shù)據(jù)庫糕非,所以我們裝載自己的永久存儲器并且確保錯誤處理蒙具。
#2
實際上這只是一個封裝器。已經(jīng)通過 NSPersistentContainer
為我們創(chuàng)建了 viewContext朽肥。而且禁筏,它已經(jīng)被配置成可以接收從其他的 contexts來的保存通知。引用自 Apple公司:這個被管理的 context對象與主隊列有關(guān)衡招。(只讀)… 這個 context是被配置成可持續(xù)的篱昔,并且從其他 contexts處理保存的通知。
#3NSpersistentContainer
也給予了我們一個工廠方法始腾,它用來創(chuàng)建多個私有隊列的 contexts
州刽。我們?yōu)榱藦?fù)雜的同步目的,在這里僅使用一個浪箭,常見的后臺 context
穗椅。由工廠方法創(chuàng)建出的 Contexts
也被設(shè)定成可自動地接收和處理 NSManagedObjectContextDidSave
的廣播消息。 這是可選項奶栖。
#4NSPersistentContainer
在后臺(詳情可見 #5)為運行 Core Data stack
暴露了一個方法匹表。我們非常喜歡這個 API
的命名门坷,所以我們也為 viewContext
創(chuàng)建了類似的封裝器。
#5
正如上文提到的袍镀,這僅是一個有關(guān) performBackgroundTask
方法的封裝器默蚌,它是 NSPersistentContainer
中的一個方法。每一次它調(diào)用一個新的 context
苇羡, parivateQueueConcurrencyType
也被創(chuàng)建绸吸。
注意: 我們已討論了大部分 NSPersistentContainer
的特性,但是你也可以查看[參考資料]宣虾,去查閱完整的內(nèi)容。
如果 NSPersistentContinainer
對我來說還是太龐大温数?
有一些可選項绣硝。
首先,確保查閱了完整的參考資料撑刺,并且在尋找你所需要的屬性或者方法鹉胖。我們已經(jīng)涵蓋了兩個初始化器,一個僅需要字符串名和完整采用 NSManagedObjectModel
的快捷方法够傍。
之后甫菠,你可以調(diào)查擴展或者子類。舉個例子冕屯,在我們其中一個項目中寂诱,我們在核心程序和擴展程序之間共享了一個 Core Data stack
。它不得不落地在一個 App 共享組群空間中安聘,并且 NSPersistentContainer
默認的文檔目錄已經(jīng)不再為我們所用痰洒。
幸運的是,通過一個輕量的子類 NSPersistentContainer
浴韭,我們又滿血復(fù)活了丘喻,并且能繼續(xù)使用那些容器類帶來的好處。
struct CoreDataServiceConsts {
static let applicationGroupIdentifier = "group.com.identifier.app-name"
}
final class PersistentContainer: NSPersistentContainer {
internal override class func defaultDirectoryURL() -> URL {
var url = super.defaultDirectoryURL()
if let newURL =
FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: CoreDataServiceConsts.applicationGroupIdentifier) {
url = newURL
}
return url
}
}
總結(jié) & 參考文獻
我希望你們喜歡這篇有關(guān) NSPersistentContainer
的簡短精干的文章念颈,并且我們也希望看到你們是如何通過這些在 Core Data
框架上的改進來演進你們的 Core Data stack泉粉。
稍等一下… 啊榴芳?還有其他的改變么嗡靡?
是的,當然有窟感。最佳的方法是通過 Apple公司的官方推文 ‘Core Data 在 iOS 10 上的新特性’叽躯。這些改變從并發(fā)、context版本肌括、請求獲取点骑、自動融合來自父 context 變化等開始酣难,以在 macOS 10.12中的 NSFetchResultsController結(jié)束。