第三遍看Core Data Programming Guide

雖然從接觸iOS開發開始,做的每一個項目都在用Core Data,但是一些比較底層的東西都是boss寫的或者用的是Restkit這個開源項目。所以雖然之前看過兩遍Core Data Programming Guide,還有很多不理解或者不熟練或者做得不好的地方。所以第三遍看,想要把以前不理解的地方弄得清晰一點。

Core Data框架是獨立于Cocoa的,所以對于沒有界面的程序,也可以應用Core Data。為了方便,這里我自己試驗的代碼都是在一個Command Line tool工程里的0.0


關于Persistent Stack



對象和外部數據存儲,這兩者之間的媒介,被整體叫做persistence stack。其中,managed object context位于棧頂,persistent object store位于棧底,中間的是persistent store coordinator。

Persistent stack

實際上,是persistent store coordinator決定著這個棧。它使用了facade模式,使得棧底的多個persistent store,在呈現給context的時候,就像一個整體一樣。
一個coordinator只能和一個managed object model相關聯。


關于Managed Object Model



一個managed object model是NSManagedObjectModel類的實例。它描述了第三方app中需要使用到的一系列entity,和多個entity之間的關系。
一個model中可能有很多NSEntityDescription對象來代表這個model的各個entity。對于每個entity來說,有兩個很重要的特性,一個是這個entity的名字,另一個是在運行時,表示這個entity的類的名字

一個entity可能會有attribute、relationship,也可能有fetched property,這三者統稱為property。需要注意的是,property不能和NSObject或NSManagedObject已有的方法名重疊,比如,不能給某個property起名為“description”。
比較特殊的一種property叫做transient property,它是不會被保存到persistent store中去的。

多個entity之間可能會有繼承關系,也可能某個entity會被指定為抽象的。

大多數model中的元素(比如entity、attribute、relationship)都會有一個對應的user info。


創建一個model


使用Xcode創建model



在Xcode中,選擇File->New->File->Core Data->Data Model就可以創建一個擴展名為.xcdatamodeld的“源文件”了(實際上應該是一個目錄)。其中包含了一個擴展名為.xcdatamodel的“源文件”??梢允褂肵code的Core Data model editor,在xcdatamodel文件中編輯model的內容,比如其中包含什么樣的entity,每個entity中有什么樣的attribute,以及各個entity之間的關系,等等。

如果App更新時,需要對model進行改動,就需要創建一個新的model version。在Xcode中,選中xcdatamodeld,選擇Editor->Add Model Version,可以繼續創建其中的xcdatamodel“源文件”。

除了model中關于entity和property的各種信息,xcdatamodel還會包含一些其他信息,比如繪制的圖表的寬高排列之類的,但這些信息在運行時并沒有什么意義。所以,model文件的編譯工具momc會把運行時沒有意義的信息去掉,將xcdatamodel文件編譯成mom文件,將xcdatamodeld目錄編譯成momd目錄。

在Xcode中找到編譯好的.app文件,右鍵Show in Finder,打開里面的內容后,可以看到其中的.momd文件夾,和這個文件夾里面的.mom文件。

如果寫的是iOS上的app,則在需要程序員自己加載model文件。有這樣兩種方法:

  1. 使用NSManagedObjectModel的initWithContentOfURL:方法。
    這是一種比較普遍使用的方法。
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:modelName withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
  1. 使用mergedModelFromBundles:方法.
    如果參數是nil,則會搜索main bundle,把其中的所有model給merge起來。
在代碼中創建\修改model



在model被一個managed object context或者一個persistent store coordinator使用之前,這個model是可以在代碼中被修改的。這允許程序員動態的創建或修改model。

試了一下在代碼中創建model:

NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init];
NSEntityDescription *launchInfoEntity = [[NSEntityDescription alloc] init];
[launchInfoEntity setName:@"LaunchInfo"];

NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];
[dateAttribute setName:@"date"];
[dateAttribute setAttributeType:NSDateAttributeType];
[dateAttribute setOptional:NO];

[launchInfoEntity setProperties:@[dateAttribute]];

[model setEntities:@[launchInfoEntity]];

如果model是在被一個managed object context或者一個persistent store coordinator使用之后,受到改動,則會拋出exception:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Can't modify an immutable model.'

Fetch Request Template



程序員可以使用NSFetchRequest類來描述從持久化存儲中取得一些對象的請求。在實際的開發中,同樣或相似的請求往往會被執行多次,所以,程序員可以自定義一些fetch request template,并把它們存到model中??梢允褂肵code的Core Data model editor,也可以在代碼中定義。

使用Core Date model editor定義fetch request template



Editor->Add FetchRequest來新建一個fetch request。

填寫Predicate,可以使用變量。右邊欄還可以指定一些高級選項。

指定Predicate

在需要使用時,只要在代碼中取出對應的fetch request template:

NSManagedObjectModel *managedObjectModel = [[context persistentStoreCoordinator] managedObjectModel];
NSFetchRequest *fetchRequest = [managedObjectModel fetchRequestFromTemplateWithName:@"fetchLaunchInfoBeforeSomeDate"
                                                              substitutionVariables:@{@"DATE" : [NSDate date]}];
NSArray *fetchResult = [context executeFetchRequest:fetchRequest error:&error];

就可以正常使用了。

直接在代碼中創建fetch request template



也可以完全動態的創建fetch request template:

NSManagedObjectModel *managedObjectModel = [[context persistentStoreCoordinator] managedObjectModel];
NSFetchRequest *fetchRequestTemplate = [[NSFetchRequest alloc] initWithEntityName:@"LaunchInfo"];
[fetchRequestTemplate setPredicate:[NSPredicate predicateWithFormat:@"date > $DATE"]];
[managedObjectModel setFetchRequestTemplate:fetchRequestTemplate forName:@"fetchLaunchInfoAfterSomeDate"];

關于Configuration



如果程序員想要把不同的entity存放到不同的persistent store中去,應該怎么做呢?一個coordinator只能對應一個managed object model,所以在默認情況下,每一個與這個coordinator相關聯的persistent store,都存放了同樣的entity。為了避免這樣的限制,可以使用Configuration來指定每個persistent store中應該存放哪些entity。
指定了Configuration之后,當程序員取這些對象的時候,它們會自動從不同的文件中被取出;保存時,它們也會被自動保存到不同的文件。

一個configuration由名字和若干entity組成??梢栽诖a中用

setEntities:forConfiguration:

方法動態的定義configuration;

也可以在Core Data editor tool中定義:


指定Configuration

每當給coordinator增加persistent store的時候,只用在configuration參數中指定對應的configuration即可以使用:

if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType
                               configuration:@"ExitInfoConfiguration"
                                         URL:exitInfoStoreURL
                                     options:nil
                                       error:&error]) {
    //Handle error
}

if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType
                               configuration:@"LaunchInfoConfiguration"
                                         URL:launchInfoStoreURL
                                     options:nil
                                       error:&error]) {
    //Handle error
}

關于Managed Object



一個managed object代表的是一個entity的實例。

每個managed object與一個managed object context相關聯。在一個特定的context中,持久化存儲中的一個特定的記錄,只能有一個對應的managed object,這種技術叫做Uniquing。但是,也可能有多個context,每個context都持有一個表示同一條記錄的managed object。


關于accessor方法



可以使用Xcode根據xcdatamodel中的內容自動生成NSManagedObject的子類。在子類的實現中,我們能看到,property被@dynamic修飾了。那是因為Core Data會在運行時動態生成accessor方法,這樣生成的accessor方法是比較高效的,也就是說,程序員一般不需要寫自定義的accessor方法。

也可以通過key-value的形式來獲取或設置attributes的值,但是在性能上KVC不如accessor方法,所以只應該在必要的情況下使用。

如果這個managed object有to-many relationship,很多時候,程序員可能會需要增添、刪除或改動這個to-many relationship中的某幾個元素,這個時候則應該使用mutableSetValueForKey:方法或者動態生成的relationship mutator方法。


關于Managed Object的生命周期



一個managed object的生命周期和標準的Cocoa對象的生命周期不太一樣,因為那是由Core Data來管理的。一個managed object表示的數據的生命周期,和這個manged object的實例的生命周期是獨立的。

可以通過一個managed object得到它所在的context,也可以通過一個context得到其中的managed object。但是默認情況下,managed object和context之間的引用是弱引用。然而有一種例外情況,context會對“被改動過的”managed object持強引用,這里的改動包括插入、刪除和修改,直到context被save、reset或者rollback。同時,undo manager也會用強引用來維持被改動過的managed object。
可以用setRetainsRegisteredObjects:方法改變這種默認情況,使得context對managed object持強引用。

當managed object有relationship的時候,它會對這個關聯的對象持強引用,這也意味著可能有強引用循環出現。所以,當使用完一個managed object的時候,應該用refreshObject:mergeChanges:方法讓它成為一個fault。

在一個managed object被創建的時候,其中每個property的值是在對應的entity中的default value。如果需要做一些自定義的初始化,建議重寫:awakeFromInsert或者awakeFromFetch方法。

其中,awakeFromInsert會在調用了initWithEntity:insertIntoManagedObjectContext:或者insertNewObjectForEntityForName:inManagedObjectContext:方法之后立刻被調用。所以,重寫這個方法,主要是可以為managed object中的property提供特殊的默認值,比如這個對象被創建的時間。

awakeFromFetch方法會在managed object從一個持久化存儲中被取出來的時候調用。重寫這個方法,可以用于建立transient值和緩存。需要注意的是,如果在這個方法中,改變了managed object中某些property,context不會被認為是dirty的。這也就意味著不應該在這個方法中操縱relationship,因為目標對象不會為此做出應有的改變。

initWithEntity:insertIntoManagedObjectContext: 這個方法也可以重寫,但是并不鼓勵這樣做。因為在重寫的這個方法中改變的狀態,可能會不支持undo和redo。

在需要“析構”的時候,不應該重寫dealloc方法,而是應該重寫didTurnInfoFault方法。這個方法會在managed object變成fault的時候被調用,也就是說會比真正的析構早一些。


關于Relationship



大多數的relationship天生就是雙向的(一個主要的例外就是fetched property)。一般來說,在使用Core Data的時候,也應該為relationship指定反向關系,這樣可以確保object graph的一致性。

一個relationship是有delete rule的。這指定了當這個對象即將被刪除的時候應該發生的行為。有這樣幾種delete rule:

  1. Deny
    如果至少有一個relationship的目的對象存在,源對象是不能被刪除的;

  2. Nullify
    在刪除當前對象的同時,將relationship的目的對象的反向關系設置為null;

  3. Cascade
    在刪除當前對象的同時,也刪除relationship的目的對象;

  4. No Action
    在刪除當前對象的同時,對relationship的目的對象不做任何操作。在使用這個delete rule的時候,程序員有責任自行維護object graph,所以應該將對應的反向關系設置成有意義的值。


關于Object ID



一個NSManagedObjectID對象是managed object的全局ID。Object ID有臨時和持久之分。當一個managed object剛剛被創建時,它將獲得一個臨時的object ID;只有當它被保存到持久化存儲中時,它才會被賦予一個持久的ID。

Object ID也可以被轉化成URI??梢允褂?managedObjectIDForURIRepresentation:方法或objectWithID:方法通過URI或ID獲取對應的managed object。


關于Validation



Validation機制用于檢驗managed object的property的值是否滿足一定條件。有兩種validation的類型,分別是:

  1. property層次的validation
  2. property之間的validation

Core Data允許程序員在managed object model中設定簡單的validation邏輯。比如,可以設置數字和日期的最大最小值,可以設置字符串的最大最小長度、需要匹配的正則表達式,還可以設置to-many relationship中數目的最大最小值。

在Core Data Model editor中可以設置一些validation邏輯

除了可以對model設置這些validation邏輯,還可以在代碼中進行自定義。

如果想要自定義property層次的validation,程序員不應該重寫validateValue:forKey:error:方法,而是應該實現validate<Key>:error:方法。
然而,如果想要自行檢查某個property是否符合規定,應該調用的是validateValue:forKey:error:方法,這個方法會將定義在managed object model中的validation邏輯也考慮進去。

也可以自定義property之間的validation。這可以通過重寫validateForUpdate:validateForInsert:validateForDelete:方法來實現。在重寫的這三個方法中,應該首先調用父類的實現。

所有的validation限制都只有在保存操作的過程中會被應用。因為managed object context的本意就是一塊草稿板,所以應該允許其中的對象有臨時性的“不合理”。


關于Faulting



一個managed object通常會用于表示被持久化存儲的數據,但是在有些情況下,一個managed object可能是fault的,也就是說它的property還沒有從外部數據存儲中載入進來。這是Core Data用于減少內存占用的一種機制。

當訪問到一個managed object的某個持久化的property的時候,fault被觸發了,如果內存中的cache沒有被擊中的話,數據會被自動從持久化存儲中取過來,這里的開銷是比較昂貴的。

需要注意的是,description方法是不會觸發fault的,所以打印剛剛取出來的managed object可以看到“<fault>”字樣。
比如這樣:

"<LaunchInfo: 0x10060b450> (entity: LaunchInfo; id: 0x40000b <x-coredata://4973AB39-0CD8-4480-AA07-7A3A877BE87D/LaunchInfo/p1> ; data: <fault>)"

如果重寫description方法,并在其中訪問了某個持久化的property,則fault會被觸發。所以應該盡量避免這樣的做法。

可以使用refreshObject:mergeChanges:并傳人參數no讓一個managed object變成fault。但是必須保證其中的relationship沒有被改變。


關于Fetching


取得指定的對象

如果app使用了多個context,那么程序員可能就需要測試一個對象是否已經從persistent store中被刪除了。這時,可以創建一個fetch request,其中這樣指定predicate:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self == %@", targetObject];

這樣就可以通過判斷fetch到的對象的數目是否為0來判斷目標對象是否已被刪除。其中的targetObject可以是一個managed object,也可以是一個manged object ID。
如果一次需要測試多個目標對象是否被刪除,可以使用更高效的IN操作符:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self IN %@", arrayOfManagedObjectIDs];
獲取特定的值



有的時候,程序員可能不需要獲取整個managed object,而是只是需要其中的某個attribute。NSExpressionDescription可以幫助程序員取得需要的值。
這時,需要使用setResultType:方法來指定這個fetch返回的結果類型是NSDictionaryResultType;還需要創建NSExpressionDescription的實例,來指定哪些property是需要取得的。
官方文檔里有示例代碼,偷個懶。


還欠缺的部分



這篇博客真是拖著寫了好久。
但是還有好多內容沒有理解,因為偷懶+之前在工作中對這些部分接觸不多沒什么感受,所以先放在這里,等下一遍看的時候,再慢慢理解好了。

Localizing a Managed Object Model
Copying and Copy and Paste
Drag and Drop
Undo Management
Ensuring Data Is Up-to-Date
Change and Undo Management
Fetched Properties
Non-Standard Persistent Attributes
Associate Metadata With a Store to Provide Additional Information and Support Spotlight Indexing
Core Data and Cocoa Bindings
Change Management
Persistent Store Features
Core Data Performance
Troubleshooting Core Data
Efficiently Importing Data

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容