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爲你帶來的一切,而且能加速你的開發週期。

相關文章
相關標籤/搜索