CoreData之MagicalRecord源码解读

CoreData之MagicalRecord源码解读

CoreData 与SQLite

说到数据持久化,很难让人不想到又爱又恨的CoreData,说到CoreData可能大多数人就是想到的繁琐,最直接的原因就是使用CoreData涉及的类特别多,再想想SQLite 就没有那么多的对象。要是说到这两者怎么选择的话,我还是会选择CoreData,原因有以下几点:
- CoreData是苹果官方推荐的持久化存储技术
- CoreData面向对象编程,更符合项目代码风格
- 以可视化界面的形式定义数据模型
- 支持KVC 、KVO
- ICould的支持
- undo、redo支持
- NSFetchedResultsController的存在
当然SQLite与CoreData相比我觉得除了麻烦的sql语句之外都还好,比如占用内存会低一些,跨平台使用等。

CoreData相关的类
  • NSManagedObjectContext(被管理的数据上下文)
    操作实际内容(操作持久层)
    作用:插入数据,查询数据,删除数据,更多介绍看这里

  • NSManagedObjectModel(被管理的数据模型)
    数据库所有表格或数据结构,包含各实体的定义信息,更多介绍看这里

  • NSPersistentStoreCoordinator(持久化存储助理)
    相当于数据库的连接器
    作用:设置数据存储的名字,位置,存储方式,和存储时机(一般和它打交道时间少),更多介绍看这里

  • NSManagedObject (数据对象,与 Managed Object Context 相关联。)更多介绍看这里

  • NSEntityDescription (包含了Entity所拥有的属性,关系等信息)
    作用:可以通过NSEntityDescription创建相对应的实体对象

搞清楚这些之后就很容易了解源码的结构,基本上都是将这些类用分类的形式自己做了手脚(这里要说一下,既然NSManagedObjectContext的作用是增删改查,为什么这里的增删却被写到了NSManagedObject的分类里,想了想这样也有好处,万一我两个实体对象都在一个上下文创建,那么再操作的时候不是还得加上实体信息才能操作,这样也比较麻烦)。

CoreData三方库 MagicalRecord

既然CoreData这么‘繁琐’,那么肯定会有人去简化这些操作(毕竟程序员是不安分的),这里主要解读(也不算解读吧,随便聊一聊MagicalRecord的使用和源码实现)MagicalRecord,该库的链接在这里,需要源码的可以去下载来研究。

打开项目源码会发现有很多的文件,感觉无从下手。这时候就可以从CoreData相关的那几个类下手,去搞清楚源码的结构

  • MagicalImportFunctions.h 主要是一些自定义的工具型方法
  • MagicalRecord+Actions 主要是存储方法,里面提供了后台线程存储和当前线程存储的接口
  • MagicalRecord+ ErrorHandling 这个就主要是错误处理相关的
  • MagicalRecord + iCloud iCloud存储初始化的一些接口
  • MagicalRecord + Options 这里面全是一些配置,包括日志打印等,在初始化CoreData的时候呢,它会采用默认配置
  • MagicalRecord + Setup 初始化CoreData的接口
  • MagicalRecord + ShorthandMethods 就一个方法,利用runtime将系统的方法加上了MR的前缀
  • MagicalRecordInternal 包括库的版本号、当前的stack状态,以及清除stack的接口
  • MagicalRecordLogging 如其名,是一些打印相关的定义
  • MagicalRecordXcode7CompatibilityMacros_h 这里面就是iOS9 nullability等新特性,作者自己换了个名
  • NSEntityDescription + MagicalRecord_DataImport 相当于就是一些属性关系吧,包括获取主键,获取给定属性名的属性描述以及根据实体描述去创建对象
  • NSManagedObject + MagicalAggregation 提供查询实体数量的接口和汇总操作(avg,count,max,min,sum)
  • NSManagedObject + MagicalFinders 四大操作(增删改查)的查操作接口
  • NSManagedObject (MagicalRecord) 增删操作的接口
  • NSManagedObject (MagicalRequests) 提供NSFetchRequest相关查询接口,结合NSFetchedResultsController使用
  • NSManagedObjectContext (MagicalRecordChainSave) 提供一系列存储接口
  • NSManagedObjectContext (MagicalObserving) 对NSManagedObjectContext上下文监听
  • NSManagedObjectContext (MagicalRecord) 对于上下文相关的接口,包括创建新的上下文等
  • NSManagedObjectContext (MagicalSaves) 提供一系列存储接口(同步、异步)
  • NSManagedObjectModel (MagicalRecord) 提供一系列NSManagedObjectModel相关的操作接口
  • NSPersistentStore (MagicalRecord) 正如NSPersistentStore功能一般,提供存储地址等相关接口
  • NSPersistentStoreCoordinator (MagicalRecord) 提供存储名字、位置等相关接口
  • 还剩下一些属性关系描述的一些分类

下面就来增删改查实践一下吧

首先我们在项目中创建Person实体,并加上name 和 age属性然后在AppDelegate里面加上初始化代码,就成功创建了数据库文件

[MagicalRecord setupCoreDataStackWithStoreNamed:@"xxx.sqlite"];

===
+ (NSString *)MR_applicationStorageDirectory
{
    NSString *applicationName = [[[NSBundle mainBundle] infoDictionary] valueForKey:(NSString *)kCFBundleNameKey];
    return [[self MR_directory:NSApplicationSupportDirectory] stringByAppendingPathComponent:applicationName];
}

存储位置就在Library/Application Support下

进行增删改查操作之前,我们先要搞清楚项目的情况,这涉及到我们NSManagedObjectContext上下文的使用,并且上面说提到的存储的接口和上下文也会有很大的关联。“MagicalRecord provides a simple class method to retrieve a default NSManagedObjectContext that can be used throughout your app. This context operates on the main thread, and is great for simple, single-threaded apps.” 这是官方原话,说用[NSManagedObjectContext MR_defaultContext]方法获取的上下文来操作,非常适合在简单的、单线程APP中使用。

NSManagedObjectContext *rootContext = [self MR_contextWithStoreCoordinator:coordinator];
[self MR_setRootSavingContext:rootContext];
NSManagedObjectContext *defaultContext = [self MR_newMainQueueContext];
[self MR_setDefaultContext:defaultContext];

从上面源码可以看到defaultContext是主线程相关的上下文,而rootContext就不是主线程相关的上下文。如果数据量大,存储比较耗时的话,可能更希望在后台线程存储,于是还提供了MR_newMainQueueContext、MR_newPrivateQueueContext、MR_context等接口,这些产生的context都会以rootContext为Parent。至于异步后台线程存储的话,我们关联到非主线程相关的上下文然后调用异步存储方法即可。

存储
单独说一说存储吧,这里面的存储无非被搞成了异步还是同步存储,这里面的所有存储接口,最终实现都会调用到如下方法

- (void) MR_saveWithOptions:(MRSaveOptions)saveOptions completion:(MRSaveCompletionHandler)completion

实现里面,首先判断有没有数据改变,这里得说一下NSManagedObjectContext 的类型,NSMainQueueConcurrencyType只能在主线程使用
NSPrivateQueueConcurrencyType 只能在创建的那个线程使用,所以之后的操作就只能
performBlock: 和 performBlockAndWait:这两个方法。更多介绍看这里 于是就看到如下代码

__block BOOL hasChanges = NO;

    if ([self concurrencyType] == NSConfinementConcurrencyType)
    {
        hasChanges = [self hasChanges];
    }
    else
    {
        [self performBlockAndWait:^{
            hasChanges = [self hasChanges];
        }];
    }

    if (!hasChanges)
    {
        MRLogVerbose(@"NO CHANGES IN ** %@ ** CONTEXT - NOT SAVING", [self MR_workingName]);

        if (completion)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil);
            });
        }

        return;
    }

    BOOL shouldSaveParentContexts = ((saveOptions & MRSaveParentContexts) == MRSaveParentContexts);
    BOOL shouldSaveSynchronously = ((saveOptions & MRSaveSynchronously) == MRSaveSynchronously);
    BOOL shouldSaveSynchronouslyExceptRoot = ((saveOptions & MRSaveSynchronouslyExceptRootContext) == MRSaveSynchronouslyExceptRootContext);

    BOOL saveSynchronously = (shouldSaveSynchronously && !shouldSaveSynchronouslyExceptRoot) ||
                             (shouldSaveSynchronouslyExceptRoot && (self != [[self class] MR_rootSavingContext]));

    id saveBlock = ^{
        MRLogInfo(@"→ Saving %@", [self MR_description]);
        MRLogVerbose(@"→ Save Parents? %@", shouldSaveParentContexts ? @"YES" : @"NO");
        MRLogVerbose(@"→ Save Synchronously? %@", saveSynchronously ? @"YES" : @"NO");

        BOOL saveResult = NO;
        NSError *error = nil;

        @try
        {
            saveResult = [self save:&error];
        }
        @catch(NSException *exception)
        {
            MRLogError(@"Unable to perform save: %@", (id)[exception userInfo] ?: (id)[exception reason]);
        }
        @finally
        {
            [MagicalRecord handleErrors:error];

            if (saveResult && shouldSaveParentContexts && [self parentContext])
            {
                // Add/remove the synchronous save option from the mask if necessary
                MRSaveOptions modifiedOptions = saveOptions;

                if (saveSynchronously)
                {
                    modifiedOptions |= MRSaveSynchronously;
                }
                else
                {
                    modifiedOptions &= ~MRSaveSynchronously;
                }

                // If we're saving parent contexts, do so
                [[self parentContext] MR_saveWithOptions:modifiedOptions completion:completion];
            }
            else
            {
                if (saveResult)
                {
                    MRLogVerbose(@"→ Finished saving: %@", [self MR_description]);
                }

                if (completion)
                {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        completion(saveResult, error);
                    });
                }
            }
        }
    };

    if (saveSynchronously)
    {
        [self performBlockAndWait:saveBlock];
    }
    else
    {
        [self performBlock:saveBlock];
    }

先判断有没有变化,然后再是根据存储的方式去调用了NSManagedObjectContext自己的save: 方法,相当于这个库就给包装了一下,增加了很多的方便使用的接口,整合了一些操作。

Person * obj =  [Person MR_createEntityInContext:context];
obj.age = 20;
obj.name = @"zhangsan"
[context MR_saveToPersistentStoreAndWait];

后台线程存储的方式
Person *person = [Person MR_createEntityInContext:[NSManagedObjectContext MR_rootSavingContext]]; //非主线
[MagicalRecord saveWithBlock:^(NSManagedObjectContext * _Nonnull localContext) {
      Person *localPerson = [person MR_inContext:localContext];
      localPerson.age = 12;
      localPerson.name = @"hu";
 }];

或者
[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext * _Nonnull localContext) {
        Person *p = [Person MR_createEntityInContext:localContext];
        p.age = 100;
        p.name = @"cheater";
}];

或者根据实体描述创建
NSEntityDescription *des = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:[NSManagedObjectContext MR_rootSavingContext]];
    Person *p = (Person *)[des MR_createInstanceInContext:[NSManagedObjectContext MR_rootSavingContext]];

    [MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext * _Nonnull localContext) {
        Person *pp = [p MR_inContext:localContext];
        pp.name = @"xxx";
        pp.age = 12;
    }];


[Person MR_deleteAllMatchingPredicate:[NSPredicate predicateWithFormat:@"SELF.name contains[c] 'laowang'"]];

关于NSPredicate(谓词)可以看看这里,可以根据谓词搜索选择删除想要删除的数据

谓词搜索
Person *obj1 = [Person MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:@"SELF.age = 20"] sortedBy:nil ascending:YES andRetrieveAttributes:nil];

根据属性搜索
 Person *ob = [Person MR_findByAttribute:@"name" withValue:@"zhangsan"];

至于NSFetchRequest相关的接口就不细说了,因为上面的查找实现就是利用NSFetchRequest去实现的,至于修改数据的话,先查找到需要修改的修改之后存储即可(记得要删除原数据,我也不知道怎么没有update接口)

汇总操作

id money = [Book MR_aggregateOperation:@"sum:" onAttribute:@"price" withPredicate:[NSPredicate predicateWithFormat:@"SELF.price > 50"]];

增删改查都说完,还说什么啊,没了吧~!那就完了吧


本文只是自己使用之后的经验之谈,如有错误之处欢迎留言,共同进步!!

照旧来张图片压压惊
压压惊.jpg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

huhansome

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值