雖然從接觸iOS開發(fā)開始湾趾,做的每一個(gè)項(xiàng)目都在用Core Data芭商,但是一些比較底層的東西都是boss寫的或者用的是Restkit這個(gè)開源項(xiàng)目。所以雖然之前看過兩遍Core Data Programming Guide搀缠,還有很多不理解或者不熟練或者做得不好的地方铛楣。所以第三遍看,想要把以前不理解的地方弄得清晰一點(diǎn)艺普。
Core Data框架是獨(dú)立于Cocoa的簸州,所以對(duì)于沒有界面的程序,也可以應(yīng)用Core Data歧譬。為了方便勿侯,這里我自己試驗(yàn)的代碼都是在一個(gè)Command Line tool工程里的0.0
關(guān)于Persistent Stack
對(duì)象和外部數(shù)據(jù)存儲(chǔ),這兩者之間的媒介缴罗,被整體叫做persistence stack。其中祭埂,managed object context位于棧頂面氓,persistent object store位于棧底,中間的是persistent store coordinator蛆橡。
實(shí)際上舌界,是persistent store coordinator決定著這個(gè)棧。它使用了facade模式泰演,使得棧底的多個(gè)persistent store呻拌,在呈現(xiàn)給context的時(shí)候,就像一個(gè)整體一樣睦焕。
一個(gè)coordinator只能和一個(gè)managed object model相關(guān)聯(lián)藐握。
關(guān)于Managed Object Model
一個(gè)managed object model是NSManagedObjectModel類的實(shí)例。它描述了第三方app中需要使用到的一系列entity垃喊,和多個(gè)entity之間的關(guān)系猾普。
一個(gè)model中可能有很多NSEntityDescription對(duì)象來代表這個(gè)model的各個(gè)entity。對(duì)于每個(gè)entity來說本谜,有兩個(gè)很重要的特性初家,一個(gè)是這個(gè)entity的名字,另一個(gè)是在運(yùn)行時(shí),表示這個(gè)entity的類的名字溜在。
一個(gè)entity可能會(huì)有attribute陌知、relationship,也可能有fetched property掖肋,這三者統(tǒng)稱為property仆葡。需要注意的是,property不能和NSObject或NSManagedObject已有的方法名重疊培遵,比如浙芙,不能給某個(gè)property起名為“description”。
比較特殊的一種property叫做transient property籽腕,它是不會(huì)被保存到persistent store中去的嗡呼。
多個(gè)entity之間可能會(huì)有繼承關(guān)系,也可能某個(gè)entity會(huì)被指定為抽象的皇耗。
大多數(shù)model中的元素(比如entity南窗、attribute、relationship)都會(huì)有一個(gè)對(duì)應(yīng)的user info郎楼。
創(chuàng)建一個(gè)model
使用Xcode創(chuàng)建model
在Xcode中万伤,選擇File->New->File->Core Data->Data Model就可以創(chuàng)建一個(gè)擴(kuò)展名為.xcdatamodeld的“源文件”了(實(shí)際上應(yīng)該是一個(gè)目錄)。其中包含了一個(gè)擴(kuò)展名為.xcdatamodel的“源文件”呜袁〉新颍可以使用Xcode的Core Data model editor,在xcdatamodel文件中編輯model的內(nèi)容阶界,比如其中包含什么樣的entity虹钮,每個(gè)entity中有什么樣的attribute,以及各個(gè)entity之間的關(guān)系膘融,等等芙粱。
如果App更新時(shí),需要對(duì)model進(jìn)行改動(dòng)氧映,就需要?jiǎng)?chuàng)建一個(gè)新的model version春畔。在Xcode中,選中xcdatamodeld岛都,選擇Editor->Add Model Version律姨,可以繼續(xù)創(chuàng)建其中的xcdatamodel“源文件”。
除了model中關(guān)于entity和property的各種信息臼疫,xcdatamodel還會(huì)包含一些其他信息线召,比如繪制的圖表的寬高排列之類的,但這些信息在運(yùn)行時(shí)并沒有什么意義多矮。所以缓淹,model文件的編譯工具momc會(huì)把運(yùn)行時(shí)沒有意義的信息去掉哈打,將xcdatamodel文件編譯成mom文件,將xcdatamodeld目錄編譯成momd目錄讯壶。
在Xcode中找到編譯好的.app文件料仗,右鍵Show in Finder,打開里面的內(nèi)容后伏蚊,可以看到其中的.momd文件夾立轧,和這個(gè)文件夾里面的.mom文件。
如果寫的是iOS上的app躏吊,則在需要程序員自己加載model文件氛改。有這樣兩種方法:
- 使用NSManagedObjectModel的initWithContentOfURL:方法。
這是一種比較普遍使用的方法比伏。
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:modelName withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
- 使用
mergedModelFromBundles:
方法.
如果參數(shù)是nil胜卤,則會(huì)搜索main bundle,把其中的所有model給merge起來赁项。
在代碼中創(chuàng)建\修改model
在model被一個(gè)managed object context或者一個(gè)persistent store coordinator使用之前葛躏,這個(gè)model是可以在代碼中被修改的。這允許程序員動(dòng)態(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是在被一個(gè)managed object context或者一個(gè)persistent store coordinator使用之后舰攒,受到改動(dòng),則會(huì)拋出exception:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Can't modify an immutable model.'
Fetch Request Template
程序員可以使用NSFetchRequest類來描述從持久化存儲(chǔ)中取得一些對(duì)象的請(qǐng)求悔醋。在實(shí)際的開發(fā)中摩窃,同樣或相似的請(qǐng)求往往會(huì)被執(zhí)行多次,所以芬骄,程序員可以自定義一些fetch request template猾愿,并把它們存到model中〉旅担可以使用Xcode的Core Data model editor,也可以在代碼中定義椎麦。
使用Core Date model editor定義fetch request template
Editor->Add FetchRequest來新建一個(gè)fetch request宰僧。
填寫Predicate,可以使用變量观挎。右邊欄還可以指定一些高級(jí)選項(xiàng)琴儿。
在需要使用時(shí),只要在代碼中取出對(duì)應(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
也可以完全動(dòng)態(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)該怎么做呢?一個(gè)coordinator只能對(duì)應(yīng)一個(gè)managed object model雄嚣,所以在默認(rèn)情況下晒屎,每一個(gè)與這個(gè)coordinator相關(guān)聯(lián)的persistent store喘蟆,都存放了同樣的entity。為了避免這樣的限制鼓鲁,可以使用Configuration來指定每個(gè)persistent store中應(yīng)該存放哪些entity蕴轨。
指定了Configuration之后,當(dāng)程序員取這些對(duì)象的時(shí)候骇吭,它們會(huì)自動(dòng)從不同的文件中被取出橙弱;保存時(shí),它們也會(huì)被自動(dòng)保存到不同的文件燥狰。
一個(gè)configuration由名字和若干entity組成棘脐。可以在代碼中用
setEntities:forConfiguration:
方法動(dòng)態(tài)的定義configuration龙致;
也可以在Core Data editor tool中定義:
每當(dāng)給coordinator增加persistent store的時(shí)候蛀缝,只用在configuration參數(shù)中指定對(duì)應(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
一個(gè)managed object代表的是一個(gè)entity的實(shí)例。
每個(gè)managed object與一個(gè)managed object context相關(guān)聯(lián)净当。在一個(gè)特定的context中内斯,持久化存儲(chǔ)中的一個(gè)特定的記錄,只能有一個(gè)對(duì)應(yīng)的managed object像啼,這種技術(shù)叫做Uniquing俘闯。但是,也可能有多個(gè)context忽冻,每個(gè)context都持有一個(gè)表示同一條記錄的managed object真朗。
關(guān)于accessor方法
可以使用Xcode根據(jù)xcdatamodel中的內(nèi)容自動(dòng)生成NSManagedObject的子類。在子類的實(shí)現(xiàn)中僧诚,我們能看到遮婶,property被@dynamic
修飾了。那是因?yàn)镃ore Data會(huì)在運(yùn)行時(shí)動(dòng)態(tài)生成accessor方法湖笨,這樣生成的accessor方法是比較高效的旗扑,也就是說,程序員一般不需要寫自定義的accessor方法慈省。
也可以通過key-value的形式來獲取或設(shè)置attributes的值臀防,但是在性能上KVC不如accessor方法,所以只應(yīng)該在必要的情況下使用边败。
如果這個(gè)managed object有to-many relationship袱衷,很多時(shí)候,程序員可能會(huì)需要增添笑窜、刪除或改動(dòng)這個(gè)to-many relationship中的某幾個(gè)元素致燥,這個(gè)時(shí)候則應(yīng)該使用mutableSetValueForKey:
方法或者動(dòng)態(tài)生成的relationship mutator方法。
關(guān)于Managed Object的生命周期
一個(gè)managed object的生命周期和標(biāo)準(zhǔn)的Cocoa對(duì)象的生命周期不太一樣排截,因?yàn)槟鞘怯蒀ore Data來管理的嫌蚤。一個(gè)managed object表示的數(shù)據(jù)的生命周期辐益,和這個(gè)manged object的實(shí)例的生命周期是獨(dú)立的。
可以通過一個(gè)managed object得到它所在的context搬葬,也可以通過一個(gè)context得到其中的managed object荷腊。但是默認(rèn)情況下,managed object和context之間的引用是弱引用急凰。然而有一種例外情況女仰,context會(huì)對(duì)“被改動(dòng)過的”managed object持強(qiáng)引用,這里的改動(dòng)包括插入抡锈、刪除和修改疾忍,直到context被save、reset或者rollback床三。同時(shí)一罩,undo manager也會(huì)用強(qiáng)引用來維持被改動(dòng)過的managed object。
可以用setRetainsRegisteredObjects:
方法改變這種默認(rèn)情況撇簿,使得context對(duì)managed object持強(qiáng)引用聂渊。
當(dāng)managed object有relationship的時(shí)候,它會(huì)對(duì)這個(gè)關(guān)聯(lián)的對(duì)象持強(qiáng)引用四瘫,這也意味著可能有強(qiáng)引用循環(huán)出現(xiàn)汉嗽。所以,當(dāng)使用完一個(gè)managed object的時(shí)候找蜜,應(yīng)該用refreshObject:mergeChanges:
方法讓它成為一個(gè)fault饼暑。
在一個(gè)managed object被創(chuàng)建的時(shí)候,其中每個(gè)property的值是在對(duì)應(yīng)的entity中的default value洗做。如果需要做一些自定義的初始化弓叛,建議重寫:awakeFromInsert
或者awakeFromFetch
方法。
其中诚纸,awakeFromInsert
會(huì)在調(diào)用了initWithEntity:insertIntoManagedObjectContext:
或者insertNewObjectForEntityForName:inManagedObjectContext:
方法之后立刻被調(diào)用撰筷。所以,重寫這個(gè)方法畦徘,主要是可以為managed object中的property提供特殊的默認(rèn)值毕籽,比如這個(gè)對(duì)象被創(chuàng)建的時(shí)間。
awakeFromFetch
方法會(huì)在managed object從一個(gè)持久化存儲(chǔ)中被取出來的時(shí)候調(diào)用旧烧。重寫這個(gè)方法影钉,可以用于建立transient值和緩存画髓。需要注意的是掘剪,如果在這個(gè)方法中,改變了managed object中某些property奈虾,context不會(huì)被認(rèn)為是dirty的夺谁。這也就意味著不應(yīng)該在這個(gè)方法中操縱relationship廉赔,因?yàn)槟繕?biāo)對(duì)象不會(huì)為此做出應(yīng)有的改變。
initWithEntity:insertIntoManagedObjectContext:
這個(gè)方法也可以重寫匾鸥,但是并不鼓勵(lì)這樣做蜡塌。因?yàn)樵谥貙懙倪@個(gè)方法中改變的狀態(tài),可能會(huì)不支持undo和redo勿负。
在需要“析構(gòu)”的時(shí)候馏艾,不應(yīng)該重寫dealloc
方法,而是應(yīng)該重寫didTurnInfoFault
方法奴愉。這個(gè)方法會(huì)在managed object變成fault的時(shí)候被調(diào)用琅摩,也就是說會(huì)比真正的析構(gòu)早一些。
關(guān)于Relationship
大多數(shù)的relationship天生就是雙向的(一個(gè)主要的例外就是fetched property)锭硼。一般來說房资,在使用Core Data的時(shí)候,也應(yīng)該為relationship指定反向關(guān)系檀头,這樣可以確保object graph的一致性轰异。
一個(gè)relationship是有delete rule的。這指定了當(dāng)這個(gè)對(duì)象即將被刪除的時(shí)候應(yīng)該發(fā)生的行為暑始。有這樣幾種delete rule:
Deny
如果至少有一個(gè)relationship的目的對(duì)象存在搭独,源對(duì)象是不能被刪除的;Nullify
在刪除當(dāng)前對(duì)象的同時(shí)蒋荚,將relationship的目的對(duì)象的反向關(guān)系設(shè)置為null戳稽;Cascade
在刪除當(dāng)前對(duì)象的同時(shí),也刪除relationship的目的對(duì)象期升;No Action
在刪除當(dāng)前對(duì)象的同時(shí)惊奇,對(duì)relationship的目的對(duì)象不做任何操作。在使用這個(gè)delete rule的時(shí)候播赁,程序員有責(zé)任自行維護(hù)object graph颂郎,所以應(yīng)該將對(duì)應(yīng)的反向關(guān)系設(shè)置成有意義的值。
關(guān)于Object ID
一個(gè)NSManagedObjectID對(duì)象是managed object的全局ID容为。Object ID有臨時(shí)和持久之分乓序。當(dāng)一個(gè)managed object剛剛被創(chuàng)建時(shí),它將獲得一個(gè)臨時(shí)的object ID坎背;只有當(dāng)它被保存到持久化存儲(chǔ)中時(shí)替劈,它才會(huì)被賦予一個(gè)持久的ID。
Object ID也可以被轉(zhuǎn)化成URI得滤≡上祝可以使用 managedObjectIDForURIRepresentation:
方法或objectWithID:
方法通過URI或ID獲取對(duì)應(yīng)的managed object。
關(guān)于Validation
Validation機(jī)制用于檢驗(yàn)managed object的property的值是否滿足一定條件懂更。有兩種validation的類型眨业,分別是:
- property層次的validation
- property之間的validation
Core Data允許程序員在managed object model中設(shè)定簡單的validation邏輯急膀。比如,可以設(shè)置數(shù)字和日期的最大最小值龄捡,可以設(shè)置字符串的最大最小長度卓嫂、需要匹配的正則表達(dá)式,還可以設(shè)置to-many relationship中數(shù)目的最大最小值聘殖。
除了可以對(duì)model設(shè)置這些validation邏輯晨雳,還可以在代碼中進(jìn)行自定義。
如果想要自定義property層次的validation奸腺,程序員不應(yīng)該重寫validateValue:forKey:error:
方法悍募,而是應(yīng)該實(shí)現(xiàn)validate<Key>:error:
方法。
然而洋机,如果想要自行檢查某個(gè)property是否符合規(guī)定坠宴,應(yīng)該調(diào)用的是validateValue:forKey:error:
方法,這個(gè)方法會(huì)將定義在managed object model中的validation邏輯也考慮進(jìn)去绷旗。
也可以自定義property之間的validation喜鼓。這可以通過重寫validateForUpdate:
、validateForInsert:
和validateForDelete:
方法來實(shí)現(xiàn)衔肢。在重寫的這三個(gè)方法中庄岖,應(yīng)該首先調(diào)用父類的實(shí)現(xiàn)。
所有的validation限制都只有在保存操作的過程中會(huì)被應(yīng)用角骤。因?yàn)閙anaged object context的本意就是一塊草稿板隅忿,所以應(yīng)該允許其中的對(duì)象有臨時(shí)性的“不合理”。
關(guān)于Faulting
一個(gè)managed object通常會(huì)用于表示被持久化存儲(chǔ)的數(shù)據(jù)邦尊,但是在有些情況下背桐,一個(gè)managed object可能是fault的,也就是說它的property還沒有從外部數(shù)據(jù)存儲(chǔ)中載入進(jìn)來蝉揍。這是Core Data用于減少內(nèi)存占用的一種機(jī)制链峭。
當(dāng)訪問到一個(gè)managed object的某個(gè)持久化的property的時(shí)候,fault被觸發(fā)了又沾,如果內(nèi)存中的cache沒有被擊中的話弊仪,數(shù)據(jù)會(huì)被自動(dòng)從持久化存儲(chǔ)中取過來,這里的開銷是比較昂貴的杖刷。
需要注意的是励饵,description
方法是不會(huì)觸發(fā)fault的,所以打印剛剛?cè)〕鰜淼膍anaged object可以看到“<fault>”字樣滑燃。
比如這樣:
"<LaunchInfo: 0x10060b450> (entity: LaunchInfo; id: 0x40000b <x-coredata://4973AB39-0CD8-4480-AA07-7A3A877BE87D/LaunchInfo/p1> ; data: <fault>)"
如果重寫description
方法役听,并在其中訪問了某個(gè)持久化的property,則fault會(huì)被觸發(fā)。所以應(yīng)該盡量避免這樣的做法禾嫉。
可以使用refreshObject:mergeChanges:
并傳人參數(shù)no讓一個(gè)managed object變成fault。但是必須保證其中的relationship沒有被改變蚊丐。
關(guān)于Fetching
取得指定的對(duì)象
如果app使用了多個(gè)context熙参,那么程序員可能就需要測(cè)試一個(gè)對(duì)象是否已經(jīng)從persistent store中被刪除了。這時(shí)麦备,可以創(chuàng)建一個(gè)fetch request孽椰,其中這樣指定predicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self == %@", targetObject];
這樣就可以通過判斷fetch到的對(duì)象的數(shù)目是否為0來判斷目標(biāo)對(duì)象是否已被刪除。其中的targetObject
可以是一個(gè)managed object凛篙,也可以是一個(gè)manged object ID黍匾。
如果一次需要測(cè)試多個(gè)目標(biāo)對(duì)象是否被刪除,可以使用更高效的IN操作符:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self IN %@", arrayOfManagedObjectIDs];
獲取特定的值
有的時(shí)候呛梆,程序員可能不需要獲取整個(gè)managed object锐涯,而是只是需要其中的某個(gè)attribute。NSExpressionDescription可以幫助程序員取得需要的值填物。
這時(shí)纹腌,需要使用setResultType:
方法來指定這個(gè)fetch返回的結(jié)果類型是NSDictionaryResultType
;還需要?jiǎng)?chuàng)建NSExpressionDescription的實(shí)例滞磺,來指定哪些property是需要取得的升薯。
官方文檔里有示例代碼,偷個(gè)懶击困。
還欠缺的部分
這篇博客真是拖著寫了好久涎劈。
但是還有好多內(nèi)容沒有理解,因?yàn)橥祽?之前在工作中對(duì)這些部分接觸不多沒什么感受阅茶,所以先放在這里蛛枚,等下一遍看的時(shí)候,再慢慢理解好了脸哀。
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