从设计一个自己的CoreData库角度了解CoreData

主要参考objc.io关于CoreData的文章(https://www.objc.io/issues/4-core-data/ )和magicalRecord(https://github.com/magicalpanda/MagicalRecord )的设计;ef

1.CoreData的结构:

要理解coreData,首先要了解它的结构,即它包含了哪些部分、这些部分又是怎么关联起来的。先上个图,从objc.io里盗来的图:


CoreData架构图.png

coreData是一个数据库管理框架,那么肯定要和数据库打交道,图里的File System就代表数据库文件操作;谁来直接管理这些,SQLite,可以理解coreData是建立在SQLite之上的,它内部的数据操作仍然是SQLite.
SQLite本身是一个独立的框架,coreData想要使用它管理数据文件,肯定需要一个角色来负责,这就是NSPersistentStore。看一下NSPersistentStore的一般构建方法,类似:

[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];

可以看得出,对于它来说,基本的需求是类型和地址,也就是决定在什么位置建立一个什么样的数据库。所以图的下半部分,或者说coreData的整体功能的一端,就是和本地的数据库文件交互。

然后,从上面看。首先是NSManagedObject,它的类代表了一种数据模型,而类的每个对象对应着一条数据。要写的舒服开心,肯定是要有数据模型的,这样存储的过程就不需要程序员自己动手了。模型具有什么属性,是什么类型,有这些,框架自身就能处理好数据以什么格式写入的问题,所以也就可以摆脱SQL语句拉,我们也就可以像使用其他类一样处理NSManagedObject了。

而NSManagedObjectContext,一般命名里带Context的,都像是一个工作场地,它们把一个操作的所需要的东西都保存进来,统一管理配置,就像绘图的CGContextRef。这里的NSManagedObjectContext就是管理NSManagedObject的,设想,你在内存里构建了一个对象,也就是一条数据,你修改它了,谁知道?删除它了,谁知道?都是NSManagedObjectContext在监控,只有把所有的增删改查都保存下来了,那么到时只要save一下,内存里改变的东西才会如实的反馈到数据库文件里。

最后,回到中间,NSPersistentStoreCoordinator;很明显,它是连接两端的纽带。所以整个流程就是:1、数据(NSManagedObject)被创建修改或删除等等,这些都被context看在眼里,然后你要保存了,context把修改的信息提交给,context根据数据信息找到正确的数据库文件,根据数据模型,正确的把数据写入到数据库文件里。2、查询时,context把查询条件提交给Coordinator,它去数据库文件里把数据查出来,给context,context再把这些数据和以在它管理内的结合。

2.构建一个CoreData stack:

上面图里的整个联系在一起的东西称为一个CoreData stack,那么怎么用代码把真个框架搭起来呢?

构建的入口在模型,模型决定了数据库表该建成啥样,context里的数据该是什么样。因为是从设计模块的角度来认识CoreData,所以还要考虑下类设计的问题:通过模型文件构建代码NSPersistentStoreCoordinator,由它再构建其它的,所以完全可以使用一个类方法,只提供一个字符串做参数,构建整个CoreData stack,那么以后使用CoreData的时候,一句话搞定。

所以我定义了一个DataManager的类,提供一个类方法:

+(BOOL)setupDataBaseWithModelName:(NSString*)modelName;
+(BOOL)setupDataBaseWithModelName:(NSString *)modelName{
    if ([DataManager shareInstance].coordinator) {
        return [DataManager shareInstance].coordinator;
    }
    //(1)构建Coordinator
    NSString* filePath = [[NSBundle mainBundle]pathForResource:modelName ofType:@"momd"];
    NSURL* modelURL = [NSURL URLWithString:filePath];
    NSAssert(modelURL, @"数据模型文件缺失");
    
    NSManagedObjectModel* model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    
    NSPersistentStoreCoordinator* coordinator = [[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:model];
    
    //(2)构建数据库文件
    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)构建context
    [NSManagedObjectContext setDefaultContextWithCoordinator:coordinator];
    
    return YES;
}

(案例代码,放在了https://github.com/FindCrt/coreDataExample_sinaWeibo, CoreData相关的代码都在coreDataModel文件夹里 )
注释标记了3个部分,第一部分构建Coordinator,有文件构建模型NSManagedObjectModel,由模型构建Coordinator;NSManagedObjectModel还有一个比较有意思的方法:

+ (nullable NSManagedObjectModel *)mergedModelFromBundles:(nullable NSArray<NSBundle *> *)bundles;

是把指定bundle里面的模型文件合成了一个模型,统一管理了,这样也好。否则得多个Coordinator,那查某个数据,还得分清是哪个Coordinator。

第二部分由Coordinator构建数据库,地址我是用一个NSPersistentStore的category方法来提供的,把文件存在沙盒的application support文件夹下,使用模型名区别。在magicalRecord里,大量的使用了category,好处是分工明确。比如一个数据库文件的地址,这个肯定是和NSPersistentStore一一对应的,也是属于它使用的东西,使用category,把这个任务交给它自身,多好。

第三部分,是构建context。为了在多线程的情况下能够使用Coredata,构建了一系列相关联的context。这个等下专门写。

基本的基础建设好,就要提供服务了:增删改查的操作。

3.增删改查服务:
//添加
+(NSManagedObject*)createEntityNamedWith:(NSString*)entityName;

+(NSManagedObject *)createEntityNamedWith:(NSString *)entityName{
    NSManagedObject* entity = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:[NSManagedObjectContext contextForCurrentThread]];
    
    return entity;
}

提供了两个参数,一个实体名、一个context,数据都要建在context来管理,肯定要指定context;然后context是关联到Coordinator的,而Coordinator有所有的模型数据,那么通过名字就可以知道这个实体的数据结构了,这样就没有问题了。

//删除
+(void)deleteEntity:(NSManagedObject*)entity;

+(void)deleteEntity:(NSManagedObject *)entity{
    [entity.managedObjectContext deleteObject:entity];
}

很简单,context有提供删除方法,只要实体的context把它自身删除即可。这个执行过程暴露了一个问题,就是数据只由context来管,如果一个数据的context没了,它就没法做任何操作了。

//修改
+(BOOL)saveContextForCurrentThead;

+(BOOL)saveContextForCurrentThead{
    return [[NSManagedObjectContext contextForCurrentThread]save];
}

对数据对象的修改,都会被记录,所以修改的关键不是修改,而是如何把内存里数据的修改同步到数据库文件里,也就是保存的问题。context本身提供了修改,考虑到多线程处理,自定义了save来做了一些处理。

//查询
+(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;
}

因为数据对象都是context在管理,所以查询也是它来处理。CoreData专门用于查询的类NSFetchRequest,它有许多的属性,可以满足丰富的查询需求,但最基本的两个是: entityName和predicate。也就是指定查哪个数据,使用什么筛选条件。

关于查询,有一个概念是fault和fire fault。当你查询的时候,数据并没有加载到内存里,得到的只是每条数据的ID,在手机里的地址等,这时数据其实是空的,称为fault;当你访问了这个数据对象的一个属性的时候,才会把实际数据加载到内存里,也就是真的用到的时候才加载。这种机制也是经常出现的吧。

从这里可以看出,我们需要处理的是上面架构图里的上部分,而CoreData帮我们把处理反馈到下部分的数据库文件里。所以操作的方向是自上而下的,然后看了下各个类的引用,果然是:
NSManagedObject有属性NSManagedObjectContext *managedObjectContext,
而NSManagedObjectContext有属性NSPersistentStoreCoordinator *persistentStoreCoordinator;
而NSPersistentStoreCoordinator有NSArray<__kindof NSPersistentStore *> *persistentStores;

4.多线程下CoreData处理:

写在下一篇了:http://www.jianshu.com/p/29d92e3d0e70

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容