第三遍看Core Data Programming Guide

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

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


關(guān)于Persistent Stack



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

Persistent stack

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


關(guān)于Managed Object Model



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

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

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

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


創(chuàng)建一個model


使用Xcode創(chuàng)建model



在Xcode中,選擇File->New->File->Core Data->Data Model就可以創(chuàng)建一個擴展名為.xcdatamodeld的“源文件”了(實際上應(yīng)該是一個目錄)。其中包含了一個擴展名為.xcdatamodel的“源文件”。可以使用Xcode的Core Data model editor,在xcdatamodel文件中編輯model的內(nèi)容,比如其中包含什么樣的entity,每個entity中有什么樣的attribute,以及各個entity之間的關(guān)系,等等。

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

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

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

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

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



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

試了一下在代碼中創(chuàng)建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類來描述從持久化存儲中取得一些對象的請求。在實際的開發(fā)中,同樣或相似的請求往往會被執(zhí)行多次,所以,程序員可以自定義一些fetch request template,并把它們存到model中。可以使用Xcode的Core Data model editor,也可以在代碼中定義。

使用Core Date model editor定義fetch request template



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

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

指定Predicate

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

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

就可以正常使用了。

直接在代碼中創(chuàng)建fetch request template



也可以完全動態(tài)的創(chuàng)建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"];

關(guān)于Configuration



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

一個configuration由名字和若干entity組成。可以在代碼中用

setEntities:forConfiguration:

方法動態(tài)的定義configuration;

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


指定Configuration

每當(dāng)給coordinator增加persistent store的時候,只用在configuration參數(shù)中指定對應(yīng)的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
}

關(guān)于Managed Object



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

每個managed object與一個managed object context相關(guān)聯(lián)。在一個特定的context中,持久化存儲中的一個特定的記錄,只能有一個對應(yīng)的managed object,這種技術(shù)叫做Uniquing。但是,也可能有多個context,每個context都持有一個表示同一條記錄的managed object。


關(guān)于accessor方法



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

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

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


關(guān)于Managed Object的生命周期



一個managed object的生命周期和標(biāo)準(zhǔn)的Cocoa對象的生命周期不太一樣,因為那是由Core Data來管理的。一個managed object表示的數(shù)據(jù)的生命周期,和這個manged object的實例的生命周期是獨立的。

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

當(dāng)managed object有relationship的時候,它會對這個關(guān)聯(lián)的對象持強引用,這也意味著可能有強引用循環(huán)出現(xiàn)。所以,當(dāng)使用完一個managed object的時候,應(yīng)該用refreshObject:mergeChanges:方法讓它成為一個fault。

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

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

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

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

在需要“析構(gòu)”的時候,不應(yīng)該重寫dealloc方法,而是應(yīng)該重寫didTurnInfoFault方法。這個方法會在managed object變成fault的時候被調(diào)用,也就是說會比真正的析構(gòu)早一些。


關(guān)于Relationship



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

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

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

  2. Nullify
    在刪除當(dāng)前對象的同時,將relationship的目的對象的反向關(guān)系設(shè)置為null;

  3. Cascade
    在刪除當(dāng)前對象的同時,也刪除relationship的目的對象;

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


關(guān)于Object ID



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

Object ID也可以被轉(zhuǎn)化成URI。可以使用 managedObjectIDForURIRepresentation:方法或objectWithID:方法通過URI或ID獲取對應(yīng)的managed object。


關(guān)于Validation



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

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

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

在Core Data Model editor中可以設(shè)置一些validation邏輯

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

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

也可以自定義property之間的validation。這可以通過重寫validateForUpdate:validateForInsert:validateForDelete:方法來實現(xiàn)。在重寫的這三個方法中,應(yīng)該首先調(diào)用父類的實現(xiàn)。

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


關(guān)于Faulting



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

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

需要注意的是,description方法是不會觸發(fā)fault的,所以打印剛剛?cè)〕鰜淼膍anaged object可以看到“<fault>”字樣。
比如這樣:

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

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

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


關(guān)于Fetching


取得指定的對象

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

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

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

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



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


還欠缺的部分



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

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

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

推薦閱讀更多精彩內(nèi)容