ios10 swift3中的coreData新使用

iOS 10 中的 NSPersistentContainer

Xcode 8 已经面世了,如果你还没有尝试过这个测试版本,你将会发现各种新东西。这里有 Swift 3 [主要的更新],有新的框架,比如 [SiriKit]和一些对现存特性的增强改进,比如 [notifications]。 我们也接收以 NSPersistentContainer
形式的简化版的 Core Data stack
,它为我们做了大部分的准备工作。它值得我们去尝试么?让我们开始深入挖掘这些新特性吧。
iOS 10
之前的 Core Data stack

多年来,在尝试了很多种 Core Data stack
之后,我们选定了两个简单的 stack
,融合成一个使用。让我们仔细看一下这些关键组件并开始连接使用他们。完整版本的 Github
链接在引用中能找到。代码已经适配到 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
发送来的通知。
#2
获取文档路径 NSURL的 getter。NSPersistentStoreCoordinator
使用它在给定的位置创建 NSPersistentStore。
#3
和文件目录相似,他获得 NSManagedObjectModel 的 getter
方法,用它来初始化有我们模型的 NSPersistentStoreCoordinator。
#4
这就是这些神奇的代码干的事情。首先,我们创建有模型的 NSPersistentStoreCoordinator。之后,我们获取我们文档目录的 url。最后,我们在这些文档目录内为某些类型的 NSPersistentStoreCoordinator
增加一个持久化的存储。
#5
我们在一个私有队列里创建一个’后台’ NSManagedObjectContext
并且把它绑定到 NSPersistentStoreCoordinator
。这个 context被用于执行同步和写操作。
#6
我们在主队列中创建一个’视图’ NSManagedObjectContext
并且把它绑定到我们的 NSPersistentStoreCoordinator
。这个 context
被用于获取显示在 UI
上的数据。
#7
这个 stack使用了稳定、成熟的融合过的 contexts,它被保存的 notifications 驱动。在这些方法中,我们执行这个融合。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)
    }
}

实际上这个更简短。但是之前版本的代码发生了什么?
简单的答案是,NSPersistentContainer
已可以为我们代劳。对于一个博客文章的解释,这肯定不够 �� 。还是容我慢慢道来。
#1
这里,我们能看到 NSPersistentContainer的能力。它完成了之前 stack
内#2, #3, #4, #5, #6 的工作,并一定程度上把我们从 #1 和 #7 中的工作中解放出来。怎么做到的?
首先,它通过一个名字来初始化,这个名字被用于在文档目录中查找一个模型并且用相同的名字创建一个存储器。这是一个快捷初始器。你也可以使用完整的版本,手动地传递你的模型。

public init(name:String,managedObjectModel model:NSManagedObjectModel)

之后,在调用 loadPersistentStores
方法之前,你还有时间来进一步配置你的容器,例如,使用 NSPersistentStoreDescription
。我们使用一个默认的 SQLite数据库,所以我们装载自己的永久存储器并且确保错误处理。
#2
实际上这只是一个封装器。已经通过 NSPersistentContainer
为我们创建了 viewContext。而且,它已经被配置成可以接收从其他的 contexts来的保存通知。引用自 Apple公司:这个被管理的 context对象与主队列有关。(只读)… 这个 context是被配置成可持续的,并且从其他 contexts处理保存的通知。

#3NSpersistentContainer
也给予了我们一个工厂方法,它用来创建多个私有队列的 contexts
。我们为了复杂的同步目的,在这里仅使用一个,常见的后台 context
。由工厂方法创建出的 Contexts
也被设定成可自动地接收和处理 NSManagedObjectContextDidSave
的广播消息。 这是可选项。
#4NSPersistentContainer
在后台(详情可见 #5)为运行 Core Data stack
暴露了一个方法。我们非常喜欢这个 API
的命名,所以我们也为 viewContext
创建了类似的封装器。
#5
正如上文提到的,这仅是一个有关 performBackgroundTask
方法的封装器,它是 NSPersistentContainer
中的一个方法。每一次它调用一个新的 context
, parivateQueueConcurrencyType
也被创建。
注意: 我们已讨论了大部分 NSPersistentContainer
的特性,但是你也可以查看[参考资料],去查阅完整的内容。
如果 NSPersistentContinainer
对我来说还是太庞大?
有一些可选项。
首先,确保查阅了完整的参考资料,并且在寻找你所需要的属性或者方法。我们已经涵盖了两个初始化器,一个仅需要字符串名和完整采用 NSManagedObjectModel
的快捷方法。
之后,你可以调查扩展或者子类。举个例子,在我们其中一个项目中,我们在核心程序和扩展程序之间共享了一个 Core Data stack
。它不得不落地在一个 App 共享组群空间中,并且 NSPersistentContainer
默认的文档目录已经不再为我们所用。
幸运的是,通过一个轻量的子类 NSPersistentContainer
,我们又满血复活了,并且能继续使用那些容器类带来的好处。

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
    }
}

总结 & 参考文献

我希望你们喜欢这篇有关 NSPersistentContainer
的简短精干的文章,并且我们也希望看到你们是如何通过这些在 Core Data
框架上的改进来演进你们的 Core Data stack。
稍等一下… 啊?还有其他的改变么?
是的,当然有。最佳的方法是通过 Apple公司的官方推文 ‘Core Data 在 iOS 10 上的新特性’。这些改变从并发、context版本、请求获取、自动融合来自父 context 变化等开始,以在 macOS 10.12中的 NSFetchResultsController结束。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容