主要參考objc.io關(guān)于CoreData的文章(https://www.objc.io/issues/4-core-data/ )和magicalRecord(https://github.com/magicalpanda/MagicalRecord )的設(shè)計(jì);ef
1.CoreData的結(jié)構(gòu):
要理解coreData,首先要了解它的結(jié)構(gòu),即它包含了哪些部分氛改、這些部分又是怎么關(guān)聯(lián)起來(lái)的游盲。先上個(gè)圖,從objc.io里盜來(lái)的圖:
coreData是一個(gè)數(shù)據(jù)庫(kù)管理框架炫贤,那么肯定要和數(shù)據(jù)庫(kù)打交道再登,圖里的File System就代表數(shù)據(jù)庫(kù)文件操作尔邓;誰(shuí)來(lái)直接管理這些,SQLite,可以理解coreData是建立在SQLite之上的锉矢,它內(nèi)部的數(shù)據(jù)操作仍然是SQLite.
SQLite本身是一個(gè)獨(dú)立的框架梯嗽,coreData想要使用它管理數(shù)據(jù)文件,肯定需要一個(gè)角色來(lái)負(fù)責(zé)沽损,這就是NSPersistentStore灯节。看一下NSPersistentStore的一般構(gòu)建方法,類似:
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];
可以看得出绵估,對(duì)于它來(lái)說(shuō)炎疆,基本的需求是類型和地址,也就是決定在什么位置建立一個(gè)什么樣的數(shù)據(jù)庫(kù)国裳。所以圖的下半部分形入,或者說(shuō)coreData的整體功能的一端,就是和本地的數(shù)據(jù)庫(kù)文件交互缝左。
然后亿遂,從上面看。首先是NSManagedObject渺杉,它的類代表了一種數(shù)據(jù)模型蛇数,而類的每個(gè)對(duì)象對(duì)應(yīng)著一條數(shù)據(jù)。要寫的舒服開心少办,肯定是要有數(shù)據(jù)模型的苞慢,這樣存儲(chǔ)的過(guò)程就不需要程序員自己動(dòng)手了诵原。模型具有什么屬性英妓,是什么類型挽放,有這些,框架自身就能處理好數(shù)據(jù)以什么格式寫入的問(wèn)題蔓纠,所以也就可以擺脫SQL語(yǔ)句拉辑畦,我們也就可以像使用其他類一樣處理NSManagedObject了。
而NSManagedObjectContext腿倚,一般命名里帶Context的纯出,都像是一個(gè)工作場(chǎng)地,它們把一個(gè)操作的所需要的東西都保存進(jìn)來(lái)敷燎,統(tǒng)一管理配置暂筝,就像繪圖的CGContextRef。這里的NSManagedObjectContext就是管理NSManagedObject的硬贯,設(shè)想焕襟,你在內(nèi)存里構(gòu)建了一個(gè)對(duì)象,也就是一條數(shù)據(jù)饭豹,你修改它了鸵赖,誰(shuí)知道?刪除它了拄衰,誰(shuí)知道它褪?都是NSManagedObjectContext在監(jiān)控,只有把所有的增刪改查都保存下來(lái)了翘悉,那么到時(shí)只要save一下茫打,內(nèi)存里改變的東西才會(huì)如實(shí)的反饋到數(shù)據(jù)庫(kù)文件里。
最后镐确,回到中間包吝,NSPersistentStoreCoordinator;很明顯源葫,它是連接兩端的紐帶诗越。所以整個(gè)流程就是:1、數(shù)據(jù)(NSManagedObject)被創(chuàng)建修改或刪除等等息堂,這些都被context看在眼里嚷狞,然后你要保存了,context把修改的信息提交給荣堰,context根據(jù)數(shù)據(jù)信息找到正確的數(shù)據(jù)庫(kù)文件床未,根據(jù)數(shù)據(jù)模型,正確的把數(shù)據(jù)寫入到數(shù)據(jù)庫(kù)文件里振坚。2薇搁、查詢時(shí),context把查詢條件提交給Coordinator渡八,它去數(shù)據(jù)庫(kù)文件里把數(shù)據(jù)查出來(lái)啃洋,給context,context再把這些數(shù)據(jù)和以在它管理內(nèi)的結(jié)合传货。
2.構(gòu)建一個(gè)CoreData stack:
上面圖里的整個(gè)聯(lián)系在一起的東西稱為一個(gè)CoreData stack,那么怎么用代碼把真?zhèn)€框架搭起來(lái)呢宏娄?
構(gòu)建的入口在模型问裕,模型決定了數(shù)據(jù)庫(kù)表該建成啥樣,context里的數(shù)據(jù)該是什么樣孵坚。因?yàn)槭菑脑O(shè)計(jì)模塊的角度來(lái)認(rèn)識(shí)CoreData,所以還要考慮下類設(shè)計(jì)的問(wèn)題:通過(guò)模型文件構(gòu)建代碼NSPersistentStoreCoordinator粮宛,由它再構(gòu)建其它的,所以完全可以使用一個(gè)類方法卖宠,只提供一個(gè)字符串做參數(shù)巍杈,構(gòu)建整個(gè)CoreData stack,那么以后使用CoreData的時(shí)候扛伍,一句話搞定秉氧。
所以我定義了一個(gè)DataManager的類,提供一個(gè)類方法:
+(BOOL)setupDataBaseWithModelName:(NSString*)modelName;
+(BOOL)setupDataBaseWithModelName:(NSString *)modelName{
if ([DataManager shareInstance].coordinator) {
return [DataManager shareInstance].coordinator;
}
//(1)構(gòu)建Coordinator
NSString* filePath = [[NSBundle mainBundle]pathForResource:modelName ofType:@"momd"];
NSURL* modelURL = [NSURL URLWithString:filePath];
NSAssert(modelURL, @"數(shù)據(jù)模型文件缺失");
NSManagedObjectModel* model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSPersistentStoreCoordinator* coordinator = [[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:model];
//(2)構(gòu)建數(shù)據(jù)庫(kù)文件
NSError* error = nil;
NSURL* storeURL = [NSPersistentStore storeURLForModelName:modelName];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];
if (error) {
NSLog(@"%@",[error localizedDescription]);
return NO;
}
[DataManager shareInstance].coordinator = coordinator;
//(3)構(gòu)建context
[NSManagedObjectContext setDefaultContextWithCoordinator:coordinator];
return YES;
}
(案例代碼蜒秤,放在了https://github.com/FindCrt/coreDataExample_sinaWeibo汁咏, CoreData相關(guān)的代碼都在coreDataModel文件夾里 )
注釋標(biāo)記了3個(gè)部分,第一部分構(gòu)建Coordinator作媚,有文件構(gòu)建模型NSManagedObjectModel攘滩,由模型構(gòu)建Coordinator;NSManagedObjectModel還有一個(gè)比較有意思的方法:
+ (nullable NSManagedObjectModel *)mergedModelFromBundles:(nullable NSArray<NSBundle *> *)bundles;
是把指定bundle里面的模型文件合成了一個(gè)模型纸泡,統(tǒng)一管理了漂问,這樣也好。否則得多個(gè)Coordinator女揭,那查某個(gè)數(shù)據(jù)蚤假,還得分清是哪個(gè)Coordinator。
第二部分由Coordinator構(gòu)建數(shù)據(jù)庫(kù)吧兔,地址我是用一個(gè)NSPersistentStore的category方法來(lái)提供的磷仰,把文件存在沙盒的application support文件夾下,使用模型名區(qū)別境蔼。在magicalRecord里灶平,大量的使用了category,好處是分工明確。比如一個(gè)數(shù)據(jù)庫(kù)文件的地址箍土,這個(gè)肯定是和NSPersistentStore一一對(duì)應(yīng)的逢享,也是屬于它使用的東西,使用category吴藻,把這個(gè)任務(wù)交給它自身瞒爬,多好。
第三部分,是構(gòu)建context侧但。為了在多線程的情況下能夠使用Coredata吆你,構(gòu)建了一系列相關(guān)聯(lián)的context。這個(gè)等下專門寫俊犯。
基本的基礎(chǔ)建設(shè)好,就要提供服務(wù)了:增刪改查的操作伤哺。
3.增刪改查服務(wù):
//添加
+(NSManagedObject*)createEntityNamedWith:(NSString*)entityName;
+(NSManagedObject *)createEntityNamedWith:(NSString *)entityName{
NSManagedObject* entity = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:[NSManagedObjectContext contextForCurrentThread]];
return entity;
}
提供了兩個(gè)參數(shù)燕侠,一個(gè)實(shí)體名、一個(gè)context,數(shù)據(jù)都要建在context來(lái)管理立莉,肯定要指定context绢彤;然后context是關(guān)聯(lián)到Coordinator的,而Coordinator有所有的模型數(shù)據(jù)蜓耻,那么通過(guò)名字就可以知道這個(gè)實(shí)體的數(shù)據(jù)結(jié)構(gòu)了茫舶,這樣就沒(méi)有問(wèn)題了。
//刪除
+(void)deleteEntity:(NSManagedObject*)entity;
+(void)deleteEntity:(NSManagedObject *)entity{
[entity.managedObjectContext deleteObject:entity];
}
很簡(jiǎn)單刹淌,context有提供刪除方法饶氏,只要實(shí)體的context把它自身刪除即可。這個(gè)執(zhí)行過(guò)程暴露了一個(gè)問(wèn)題有勾,就是數(shù)據(jù)只由context來(lái)管疹启,如果一個(gè)數(shù)據(jù)的context沒(méi)了,它就沒(méi)法做任何操作了蔼卡。
//修改
+(BOOL)saveContextForCurrentThead;
+(BOOL)saveContextForCurrentThead{
return [[NSManagedObjectContext contextForCurrentThread]save];
}
對(duì)數(shù)據(jù)對(duì)象的修改喊崖,都會(huì)被記錄,所以修改的關(guān)鍵不是修改雇逞,而是如何把內(nèi)存里數(shù)據(jù)的修改同步到數(shù)據(jù)庫(kù)文件里荤懂,也就是保存的問(wèn)題。context本身提供了修改塘砸,考慮到多線程處理节仿,自定義了save來(lái)做了一些處理。
//查詢
+(NSArray*)fetchEntity:(NSString*)entityName withPredicate:(NSPredicate*)predicate;
+(NSArray *)fetchEntity:(NSString *)entityName withPredicate:(NSPredicate *)predicate{
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:entityName];
request.predicate = predicate;
NSError* error = nil;
NSArray* result = [[NSManagedObjectContext contextForCurrentThread]executeFetchRequest:request error:&error];
if (!result) {
NSLog(@"fetch %@ error :%@",entityName,[error localizedDescription]);
return nil;
}
return result;
}
因?yàn)閿?shù)據(jù)對(duì)象都是context在管理掉蔬,所以查詢也是它來(lái)處理粟耻。CoreData專門用于查詢的類NSFetchRequest,它有許多的屬性眉踱,可以滿足豐富的查詢需求挤忙,但最基本的兩個(gè)是: entityName和predicate。也就是指定查哪個(gè)數(shù)據(jù)谈喳,使用什么篩選條件册烈。
關(guān)于查詢,有一個(gè)概念是fault和fire fault。當(dāng)你查詢的時(shí)候赏僧,數(shù)據(jù)并沒(méi)有加載到內(nèi)存里大猛,得到的只是每條數(shù)據(jù)的ID,在手機(jī)里的地址等淀零,這時(shí)數(shù)據(jù)其實(shí)是空的挽绩,稱為fault;當(dāng)你訪問(wèn)了這個(gè)數(shù)據(jù)對(duì)象的一個(gè)屬性的時(shí)候驾中,才會(huì)把實(shí)際數(shù)據(jù)加載到內(nèi)存里唉堪,也就是真的用到的時(shí)候才加載。這種機(jī)制也是經(jīng)常出現(xiàn)的吧肩民。
從這里可以看出唠亚,我們需要處理的是上面架構(gòu)圖里的上部分,而CoreData幫我們把處理反饋到下部分的數(shù)據(jù)庫(kù)文件里持痰。所以操作的方向是自上而下的毒费,然后看了下各個(gè)類的引用觉增,果然是:
NSManagedObject有屬性NSManagedObjectContext *managedObjectContext,
而NSManagedObjectContext有屬性NSPersistentStoreCoordinator *persistentStoreCoordinator;
而NSPersistentStoreCoordinator有NSArray<__kindof NSPersistentStore *> *persistentStores;