CoreData之MagicalRecord源码解读

2023-10-31

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CoreData之MagicalRecord源码解读 的相关文章

随机推荐

  • 华夏大盘精选基金经理王亚伟:08年做不好会赔钱

    编者按 华夏基金投资决策委员会主席 华夏大盘精选基金经理王亚伟在华夏基金08年投资策略报告会上对明年资本市场的走势发表了自己的看法 王亚伟 尊敬的各位领导 各位来宾 下午好 刚才两位名嘴很精彩 现在基金行业总体来说是越来越娱乐化了 基金经理
  • 12. XPath解析入门

    目录 前言 模块安装 XPath涉及的基本概念 XPath基本语法 测试完整代码 XPath进阶用法 需求1 让xpath处理这个资源文件 需求2 找到标签位置 如html标签 需求3 找到无序列表 ul 中每一项 li 包裹的文本内容 需
  • 树莓派——配置Linux内核适合树莓派

    文章目录 将linux内核代码和编译工具tools上传到ubuntu 树莓派等芯片带操作系统的启动过程 不带操作系统的 带操作系统的 BootLoader的工作流程 树莓派Linux源码目录树分析 树莓派Linux源码配置 Linux源码特
  • 【数据结构】插入排序 & 希尔排序

    目录 插入排序 希尔排序 插入排序 时间复杂度 O N 2 空间复杂度 O 1 稳定性 稳定 void InsertSort int arr int size for int i 0 i lt size 1 i int end i int
  • easyexcel 第一次导入导出会报错com.alibaba.excel.exception.ExcelAnalysisException,所以自定义excel导入导出表格

    报错原因 由于easyexcel导入导出时如果存在null会报错 跟踪源码com alibaba excel analysis v07 XlsxSaxAnalyser parseXmlSource 查看xmlReader parse 发现这
  • vi 操作

    vi filename c vi 457 filename c 打开文件同时跳至457行 exc 由输入状态退出到控制命令状态 shirt zz 保存并退出 w 保存退出 q 不保存退出 457 跳至457行 set nu 在前面列出行号
  • 大数据教育平台数据仓库系统搭建 附安装包与脚本

    一 数仓项目需求及架构设计 数据仓库是为企业所有级别的决策制定过程 提供所有类型数据支持的战略集合 数据仓库是出于分析报告和决策支持目的而创建的 为需要业务智能的企业 提供指导业务流程改进 监控时间 成本 质量以及控制 1 项目需求分析 数
  • Xilinx软件开发: 用仿真器在XSCT下加载u-boot

    XSCT介绍 XSCT全称叫做Xilinx Software Command Line Tool 顾名思义是Xilinx提供的软件命令行工具 完整的使用说明可以参考ug1208 xsct reference guide 我们平常调试裸机程序
  • Gradle 5.0 更新介绍

    Gradle 5 0正式版出来有几天了 这个工具的发展速度还真是惊人 前些天我看到gradle 5 rc版的时候还在想正式版什么时候出 没想到rc版没过几天正式版就出来了 那么正好 就来介绍一下gradle 5 0正式版加入的一些新功能吧
  • json字段 react_react 解析json

    Copyright 2013 2017 David Caldwell Permission to use copy modify and or distribute this software for any purpose with or
  • Python的sort函数和sorted、lambda和cmp

    1 sort和sorted 我们需要对List进行排序 Python提供了两个方法 对给定的List L进行排序 方法1 用List的成员函数sort进行排序 方法2 用built in函数sorted进行排序 从2 4开始 iterabl
  • STM32移植lwip之建立tcp服务器

    本篇目标 在之前能ping通pc机的工程基础上搭建tcp连接 并可以收发数据 在网络调试工具上显示 材料准备 基础工程 修改后能ping通pc机的工程 STM32官方移植lwip修改代码 调试工具 用来调试tcp连接下的数据接收 网络调试助
  • QT从入门到入土(七)——鼠标事件

    引言 个人认为 事件机制是Qt最难以理解且最为精妙的一部分 事件主要分为两种 在与用户交互时发生 比如按下鼠标 mousePressEvent 敲击键盘 keyPressEvent 等 系统自动发生 比如计时器事件 timerEvent 等
  • 嵌入式系统-CAT1025 EEPROM芯片自学报告

    一 芯片简介 CAT1025 是基于微控制器系统的存储器和电源监控的完全解决方案 它利用低功耗 CMOS 技术将 2K 位的串行 EEPOM 和用于掉电保护的系统电源监控电路集成在一块芯片内 存储器采用 400KHz 的 I2C 总线接口
  • 搜索引擎solr系列---高亮配置及问题总结

    solr的高亮配置有两种方式 一种是配置形式 具体是在配置文件中配置的 该方式我没有用过 所以我这里就不写它了 另一种就是以代码的形式 我只会用这种方式 所以只写这部分 其实还要一种就是自我实现 这个更简单粗暴 1 高亮的代码具体如下 pa
  • 数据结构—散列表(哈希表)的原理以及Java代码的实现

    本文详细介绍了散列表的概念 散列函数的选择 散列冲突的解决办法 并且最后提供了一种散列表的Java代码实现 数组的特点是寻址容易 插入和删除困难 而链表的特点是寻址困难 插入和删除容易 而对于tree结构 它们的查找都是先从根节点进行查找
  • ElasticSearch 简介及基本操作

    简介 什么是ElasticSearch ElasticSearch 简称 ES 是基于Apache Lucene构建的开源搜索引擎 是当前最流行的企业级搜索引擎 Lucene本身就可以被认为迄今为止性能最好的一款开源搜索引擎工具包 但是lu
  • vue+element-ui初体验入门拥有自己的前台项目以及配置文件讲解(1)vue项目创建

    阿丹 之前一直在写jsp页面 自从发现vue可以支持更好看更高级的页面效果之后开始研究一下 这篇文章 可以带领兄弟们简单的得到实现一个简易的自己vue项目 如果想部署服务器上 可以参考阿丹的部署vue的文章哈 准备工作 编译工具 idea
  • python后端学习(五)迭代器、生成器、协程

    迭代器 迭代是访问集合元素的一种方式 迭代器是一个可以记住遍历的位置的对象 迭代器对象从集合的第一个元素开始访问 直到所有的元素被访问完结束 迭代器只能往前不会后退 1 可迭代对象 我们已经知道可以对list tuple str等类型的数据
  • CoreData之MagicalRecord源码解读

    CoreData之MagicalRecord源码解读 CoreData 与SQLite 说到数据持久化 很难让人不想到又爱又恨的CoreData 说到CoreData可能大多数人就是想到的繁琐 最直接的原因就是使用CoreData涉及的类特