CoreData之MagicalRecord源码解读

来源:互联网 发布:淘宝怎么上架宝贝教程 编辑:程序博客网 时间:2024/06/11 08:00

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

0 0
原创粉丝点击