Core Data 概述

前言

Core Data多是OS X与iOS中最容易被误解的框架。这篇文章的意义在于让你理解Core Data的本质以及正确的使用Core Data。数据库

Core Data是啥

大苹果发布OSX 10.4的时候第一次把Core Data框架丢了出来。那时YouTube刚出来。缓存

Core Data是数据层技术(毫无疑问)。CD帮你建立数据层并反映你APP的状态。CD同时也是一个永久化(固化)技术,它能够将数据对象值存储到磁盘。可是重要一点是CD毫不仅仅是一个用来读取保存数据的框架。在设计到内存的时候,它一样可以对数据进行操做。app

若是你曾经整过关系型数据:CD绝对不属于这个范畴。它意味着更多。若是你用过SQL封装:CD据对不是类SQL得封装。CD确实是可使用SQL作存储,可是它是更高层次的一种封装。若是你是奔着前二者去的,那么CD不适合你。框架

CD最牛逼的一个功能是它提供了对象图管理。这块CD的特性是你须要去了解以便后续使用CD的时候能发挥出它得功效。性能

另一点:CD与UI级别的框架是彻底解耦的。这说明它设计之初就是彻底的数据层框架。同时在OSX平台上还设计了不少驻后台相似的功能。编码

CD是有几个模块组成的,是一种十分灵活的技术。在大多数的使用场景,建立过程相对简单。spa

当全部的模块组合到一块儿的时候,咱们通常叫它们为CD栈。这个栈主要由两部分组成。一部分是关于对象图管理,并且这部分是你须要很好理解并知道怎么使用的一块。第二部分是关于固化,好比保存数据对象的状态(值)而且再次去检索(获取)该状态(值)。翻译

在两个模块之间,栈的中间件是Persistent Store Coordinator(PSC,容我翻译成永久层切换者,属于桥接模式)。PSC将对象图管理部分与固化部分联系在了一块儿。当这两个模块须要对话的时候,这时候PSC就上场了。设计

图片描述

对象图管理正是你应用数据层逻辑活动的区域。数据层对象活动在数据上下文之中。大多数建立,只有一个数据上下文而且全部对象都在之中进行操做。CD支持多数据上下文来适应更高级的使用场景。注意到每一个上下文都是区别开来的(立刻能够接触到)。你须要记住的一个重要点是对象须要跟它们的上下文绑定在一块儿。每个被管理的对象知道本身在哪一个上下文,每一个上下文知道本身管理哪些对象。code

栈的另一部分是固化发生现场,举个栗子,CD从文件系统进行读写的发生现场。几乎全部得场景,PSC只链接这一个所谓得固化仓库,而且这个固化仓库与文件系统中的SQLite数据库进行交互。更高级一点,CD支持一个PSC链接多个数据仓库,这些数据仓库能够是不一样的存储方式而不只仅是SQLite。

最多见得场景,以下:
图片描述

如何让各组件协做

举个栗子快速解释如何协做。(这里拿做者工程。。这里不提供),假设咱们有一个实体,好比咱们拿一个实体去存一个标题。每个单件都有一个子单件,这样咱们就有数据的父子关系。

(大意)咱们经过继承NSManagedObject实例化出与实体同名对象,这样可让单件实体能够映射到单件对象上。

咱们的应用有单一的根单件。它没有什么神奇的地方。它只是简单的单件让咱们用来展现单件层级的底端。固然它再也不会有父亲结点。

当应用启动的时候,咱们建立上面所描述的栈,使用一个仓库,一个管理对象上下文,而后用一个固化仓库切换器链接它们。

在咱们第一次启动的时候,咱们没有任何数据。咱们要作的是建立根单件。你须要插入这些管理对象到上下文中。

建立对象

看上去可能很笨重,当咱们要插入对象(数据)的时候须要调用

+ (id)insertNewObjectForEntityForName:(NSString *)entityName 
               inManagedObjectContext:(NSManagedObjectContext *)context

方法做用在NSEntityDescription。咱们建议你添加两个便利的方法到模型类:

+ (NSString *)entityName
{
   return @“Item”;
}

+ (instancetype)insertNewObjectInManagedObjectContext:(NSManagedObjectContext *)moc;
{
   return [NSEntityDescription insertNewObjectForEntityForName:[self entityName] 
                                        inManagedObjectContext:moc];
}

从而咱们能够这样添加咱们的根对象:

Item *rootItem = [Item insertNewObjectInManagedObjectContext:managedObjectContext];

如今咱们管理对象上下文有了一个单件。上下文也知道新添加得对象,而且新添对象也知道本身的上下文。

保存变化

说到这个,虽然咱们尚未接触PSC以及固化仓库。新添对象,rootItem已经在内存中了。若是咱们须要保存咱们的模型对象,咱们须要保存上下文。

NSError *error = nil;
if (! [managedObjectContext save:&error]) {
    // Uh, oh. An error happened. :(
}

说到这时,有不少即将发生的。首先管理对象上下文计算出哪里发生了变化。上下文责任所在就是记录下其中任何管理对象任意以及全部的变化。在咱们的场景里,咱们作的变化就是插入了rootItem.

而后管理对象上下文传递这些变化到固化仓库切换器PSC而且让它将变化传递到数据仓库去。PSC将咱们插入的对象写到SQL中去并在磁盘中保存起来。NSPersistentStore类是真正与SQL进行交互的实体而且生成要执行的SQL代码。PSC的角色仅仅是链接仓库与上下文。在咱们的栗子中,这个角色相对简单,可是复杂的建立将涉及到多仓库多上下文。

更新关系

CD的强大之处在于管理数据关系。让咱们举个简单得栗子经过添加一个新的第二个单件来让它成为rootItem的子单件。

Item *item = [Item insertNewObjectInManagedObjectContext:managedObjectContext];
item.parent = rootItem;
item.title = @"foo";

再提一次这些变化只在上下文中发生改变。一旦咱们存储了上下文,上下文会通知PSC去添加一个新增的对象到数据库文件就想咱们第一次添加对象那样。可是它也会更新上述第二单件与第一单件的关系以及第一单件与第二单件的关系。记得怎么使单件实体之间有父子关系。一样倒置过来他们关系相反。由于咱们设置了第一个单件是第二个单件的父亲,那么第二单件是第一个单件的孩子。管理对象上下文记录这些关系而后PSC和数据仓库保存这些关系到磁盘上。

获取对象

咱们已经建立一些子单件以及子单件的子单件。而后咱们重启当咱们的APP。CD已经在数据库文件存储了这些单件之间的关系。对象图已经被固化了。咱们须要获取咱们的根单件,这样咱们能够展现单件列表的底层。有两种方式能够获取,首先咱们先来看第一种。

当咱们建立咱们的根单件对象的时候,一旦咱们保存了它,咱们能够经过它的NSManagedObjectID来访问到它。NSManagedObjectID是个晦涩的对象仅仅表示根对象。一样咱们能够将它保存到像NSUSerDefaults中去,好比:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setURL:rootItem.managedObjectID.URIRepresentation forKey:@"rootItem"];

如今当咱们的应用重启的时候,咱们能够这样获取咱们想要的对象:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSURL *uri = [defaults URLForKey:@"rootItem"];
NSManagedObjectID *moid = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:uri];
NSError *error = nil;
Item *rootItem = (id) [managedObjectContext existingObjectWithID:moid error:&error];

显然,在实际编码的时候咱们须要去肯定NSUserDefaults是否返回一个有效值。

这里发生的是管理数据上下文命令PSC去数据库获取特定的对象。根对象被取回到了上下文。可是其余对象尚未到内存中。

根单件有一个关系叫作孩子。可是如今啥都没有。咱们要展现根单件的子单件,因此咱们继续调用:

NSOrderedSet *children = rootItem.children;

此时发生的是上下文根单件的孩子关系出现了错误。CD将这关系标注做为即将解决的的事项。而后当咱们访问到这里时候,上下文会自动地根据PSC指向来获取子单件到上下文中来。

这些听起来都没啥感受,可是这里确确实实有许多工做要作。若是碰巧全部得子对象都在内存中,那么CD会肯定它会重用这些对象。这就是惟一化。在上下文中,不会有多余(仅有一个)的对象来表示已有的单件。

其次,PSC有其关于对象值内部缓存。若是上下文须要一个特定的对象(好比子单件),若是PSC在其缓存中已有这个对象,那么这个对象不用再去数据仓库加载而是被直接添加到上下文中。这十分重要,由于访问仓库意味着调用SQL的代码,这样显然从速度上跟读取已在内存的对象相比就慢多了。

咱们继续遍历咱们子单件的子单件,咱们将整个对象图搬到管理对象上下文中。一旦它们全在内存中了,操做这些对象以及遍历对象之间的关系就变得超级快了,由于咱们仅仅是在上下文中进行对象操控。咱们不须要跟PSC打交道了。这时候访问单件标题,父亲,孩子属性很是的快速并且效率很高。

理解场景中这些数据怎么抓取十分重要,由于它影响到性能。同来来讲,它可有可无,由于咱们不会处理大量的数据。可是只要涉及到了,你必须了解到底其中的过程是什么。

当你遍历关系的时候,一件事情会发生:1)对象已经在上下文中此时遍历消耗代价很小。2)对象不在上下文中,PSC缓存这个对象,由于你最近从仓库中获取过该对象。代销仍是比较小的。3)代价比较大的是对象第一次从SQL数据库获取,这样的消耗远大于1与2.

若是你知道你不得不去从数据仓库获取对象,那么你是否限制每次抓取的数据量将产生巨大的差异。在咱们栗子中,咱们可能一次性抓取全部得孩子而不是一个接一个。这活儿能够靠特定类NSFetchRequest来搞定。可是咱们必须注意仅仅是当咱们须要的时候才去调用抓取请求,由于抓取请求同样会带来上文3的消耗;它一般是要访问数据库的。因此,当涉及到性能问题,确认对象是否已经存在就变得有意义了。你能够调用来-[NSManagedObjectContext objectRegisteredForID:]来判断。

改变对象的值

如今,咱们来试着改变咱们单件对象的标题值:

item.title = @"New title";

当咱们这么作的时候,单件标题会发生改变。可是附带说下,管理对象上下文将标记特定的管理对象(这个单件)为修改过的,这样它将会被保存到PSC和附带的数据仓库中当咱们在上下文上调用-save:的时候。上下文的一个职责就是记录改变。

上下文知道哪个对象被插入,改变,或者是删除自从上一次的保存动做后。你能够进行对应的操做:-insertedObjects, -updatedObjects, and -deletedObjects。一样地,你可让被改变值的管理对象调用-changedValues方法。你也能够不用这么作。可是这是CD一般要推送反馈到数据仓库的你所作的变化。

当咱们插入一个新的单件对象,这是CD如何知道它须要推送这些变化到仓库。一样咱们改变了标题,一样的事情也就发生了。

此处略三段。。。。(偷懒)

总结

CD看上去使人生畏,那是由于它提供了须要便利却表达方式比较复杂。准则:让每件事尽可能简单。它可让开发变得简单以及让你与你的用户避免麻烦。只有在你以为更复杂的功能对你有帮助的时候才使用它们好比后台上下文。

当你使用一个简单的CD栈,而且你如上文所述那样使用它,你讲快速的学会欣赏CD为你带来的一切,而且能加速你的开发周期。

相关文章
相关标签/搜索