CoreData简介以及第三方框架MagicRecord源码解析

CoreData简介以及第三方框架MagicRecord源码解析

Posted by Ted on December 1, 2016

一、CoreData结构

可以用两张图来表示:

NSManagedObject

数据库对象,一个NSManagedObject对应一张表,NSManagedObject的一个属性对应数据表的一个字段。数据库的增删查改就是操作NSManagedObject,通过xCode->Editor->Create NSManagedObject Subclass…来创建对应表的对象model

NSManagedObjectContext

NSManagedObject操作的上下文,NSManagedObject的操作会先缓存在上下文中,还未存到磁盘中

- (NSManagedObjectContext *)managedObjectContext{
    if (__managedObjectContext != nil) {
        return __managedObjectContext;
    }
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        __managedObjectContext = [[NSManagedObjectContext alloc] init];
        [__managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return __managedObjectContext;
}

生成NSManagedObjectContext时需要设置NSPersistentStoreCoordinator

有三种类型

  • NSConfinementConcurrencyType (或者不加参数,默认就是这个)
  • NSMainQueueConcurrencyType (表示只会在主线程中执行)
  • NSPrivateQueueConcurrencyType (表示可以在子线程中执行)

通过 setParentContext 方法,可以设置另外一个 NSManagedObjectContext 为自己的父级,这个时候子级可以访问父级下所有的对象,而且子级 NSManagedObjectContext 的内容变化后,如果执行save方法,会自动的 merge 到父级 NSManagedObjectContext 中,也就是子级save后,变动会同步到父级 NSManagedObjectContext。当然这个时候父级也必须再save一次,如果父级没有父级了,那么就会直接向NSPersistentStoreCoordinator中写入,如果有就会接着向再上一层的父级冒泡……

NSPersistentStoreCoordinator

用来管理保存数据到磁盘这个操作

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator{
    if (__persistentStoreCoordinator != nil) {
        return __persistentStoreCoordinator;
    }
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"HelloApp.sqlite"];
    NSError *error = nil;
    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    
    return __persistentStoreCoordinator;
}

生成NSPersistentStoreCoordinator需要参数:文件保存路径、NSManagedObjectModel

NSManagedObjectModel

生成这个类的来源就是在 xCode 里的.xcdatamodeld文件,我们可以可视化的对这个文件进行操作,实际上这个文件也就相当于数据库的 schema,这个文件编译后就是.momd或.mom文件。

Model Vesion:版本升级,Editor->Add Model Version,生成新的momd文件来进行版本升级,并且重新生成相关类

- (NSManagedObjectModel *)managedObjectModel{
    if (__managedObjectModel != nil) {
        return __managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"HelloApp" withExtension:@"momd"];
    __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return __managedObjectModel;
}

二、多线程里的CoreData

在多线程中,每个线程都会有一个上下文NSManagedObjectContext,而NSManagedObjectContext可以共享一个NSPersistentStoreCoordinator或者,每个NSManagedObjectContext都对应一个NSPersistentStoreCoordinator,所以会有以下几种方式:

1、

这种方式,最简单明了,在子线程的privatecontext里操作完数据库对象后,将操作缓存merger到主线程的maincontext,再由maincontext通过NSPersistentStoreCoordinator存到本地磁盘。但是存到本地磁盘中是一个耗时的IO操作,对于主线程来说,这是不能忍的,所以不能用这种方式

2、

这个方式在跟第一个方式的区别在于,主线程上的maincontext与NSPersistentStoreCoordinator交互之家再插了一层子线程的privatecontext,context之间的传递是很快的,这样可以有效地避免IO阻塞主线程,而且childContext调用save方法,其parentContext不用任何merge操作,CoreData自动将数据merge到parentContext当中,这样可以保证每个context的数据同步

3、

这种情况下,privatecontext与maincontext共同连接NSPersistentStoreCoordinator,子线程中创建privateContext,进行数据增删改查操作,直接save到本地数据库,同时发出通知NSManagedObjectContextDidSaveNotification,在主线程mainContext的mergeChangesFromContextDidSaveNotification:notification方法,将所有的数据变动merge到mainContext中,这样就保持了两个Context中的数据同步。由于大部分的操作都是privateContext在子线程中操作的,所以这种设计是UI线程耗时最少的一种设计,但是它的代价是需要多写mergeChanges的方法。

三、MagicRecord源码解析

+ (void) setupCoreDataStackWithStoreAtURL:(NSURL *)storeURL
{
    if ([NSPersistentStoreCoordinator MR_defaultStoreCoordinator] != nil) return;
    
    NSPersistentStoreCoordinator *coordinator = [NSPersistentStoreCoordinator MR_coordinatorWithSqliteStoreAtURL:storeURL];
    [NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:coordinator];
    
    [NSManagedObjectContext MR_initializeDefaultContextWithCoordinator:coordinator];
}

先生成一个默认的NSPersistentStoreCoordinator,再生成默认的context,如下:

+ (void) MR_initializeDefaultContextWithCoordinator:(NSPersistentStoreCoordinator *)coordinator;
{
    NSAssert(coordinator, @"Provided coordinator cannot be nil!");
    if (MagicalRecordDefaultContext == nil)
    {
      	// NSPrivateQueueConcurrencyType
        NSManagedObjectContext *rootContext = [self MR_contextWithStoreCoordinator:coordinator];
        [self MR_setRootSavingContext:rootContext];
		
        //NSMainQueueConcurrencyType
        NSManagedObjectContext *defaultContext = [self MR_newMainQueueContext];
        [self MR_setDefaultContext:defaultContext];

        [defaultContext setParentContext:rootContext];
    }
}

MagicRecord生成了两个context:

  • rootContext:NSPrivateQueueConcurrencyType,用以保存数据的上下文
  • defaultContext:NSMainQueueConcurrencyType,用以主线程的上下文

defaultContext的父context是rootContext:RootSavingContext,可以看出MagicRecord默认用的是第二种模式,很简单就可以新建一个NSManagedObject并且保存

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSManagedObjectContext *current_context = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
        Person *person = [Person MR_createEntityInContext:current_context];
        person.name = @"jack";
        [current_context MR_saveToPersistentStoreWithCompletion:^(BOOL contextDidSave, NSError * _Nullable error) {
            NSLog(@"finish save");
        }];
    });
2016-07-25 20:26:29.454 MagicR[19718:3903431] Created new private queue context: <NSManagedObjectContext: 0x6100001da220>
2016-07-25 20:26:29.454 MagicR[19718:3903443] → Saving <NSManagedObjectContext (0x6100001da220): Untitled Context> on a background thread
2016-07-25 20:26:29.460 MagicR[19718:3903375] → Saving <NSManagedObjectContext (0x6080001daa90): MagicalRecord Default Context> on the main thread
2016-07-25 20:26:29.462 MagicR[19718:3903431] → Saving <NSManagedObjectContext (0x6180001da6d0): MagicalRecord Root Saving Context> on a background thread
2016-07-25 20:26:29.466 MagicR[19718:3903375] finish save

从MagicRecord的日志就可以看出来,整个过程如下:

  1. 在子线程新建了一个current_context,并且设置他的父context为主线程的context(default context),然后Person在子线程context改变
  2. 将current_context的变动merge到父线程的context也就是defaultcontext,主线程的context同样merge到父线程的也就是rootcontext
  3. rootcontext在子线程将变动保存到磁盘

如果想用第三种方式的话可以这样:在修改之后发出NSManagedObjectContextDidSaveNotification通知主线程的context。而主线程的context通过mergeChangesFromContextDidSaveNotification来合并修改

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSManagedObjectContext *current_context = [NSManagedObjectContext MR_newPrivateQueueContext];
        [current_context setPersistentStoreCoordinator:[NSPersistentStoreCoordinator MR_defaultStoreCoordinator]];
        Person *person = [Person MR_createEntityInContext:current_context];
        person.name = @"jack";
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(contextChanged:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:current_context];
        [current_context MR_saveToPersistentStoreWithCompletion:^(BOOL contextDidSave, NSError * _Nullable error) {
            NSLog(@"finish save");
        }];
    });
- (void)contextChanged:(NSNotification*)notification{
    [[NSManagedObjectContext MR_newPrivateQueueContext] mergeChangesFromContextDidSaveNotification:notification];
}

这样就能保证子线程的Context与主线程的context内容修改可以同步