美文网首页iOS第三方框架使用技巧iOS开发知识收录
Core Data详细解析(十一) —— MagicalReco

Core Data详细解析(十一) —— MagicalReco

作者: 刀客传奇 | 来源:发表于2018-09-29 19:05 被阅读816次

    版本记录

    版本号 时间
    V1.0 2018.09.29 星期六

    前言

    数据是移动端的重点关注对象,其中有一条就是数据存储。CoreData是苹果出的数据存储和持久化技术,面向对象进行数据相关存储。感兴趣的可以看下面几篇文章。
    1. iOS CoreData(一)
    2. iOS CoreData实现数据存储(二)
    3. Core Data详细解析(三) —— 一个简单的入门示例(一)
    4. Core Data详细解析(四) —— 一个简单的入门示例(二)
    5. Core Data详细解析(五) —— 基于多上下文的Core Data简单解析示例(一)
    6. Core Data详细解析(六) —— 基于多上下文的Core Data简单解析示例(二)
    7. Core Data详细解析(七) —— Core Data的轻量级迁移(一)
    8. Core Data详细解析(八) —— Core Data的轻量级迁移(二)
    9. Core Data详细解析(九) —— MagicalRecord框架之基本概览(一)
    10. Core Data详细解析(十) —— MagicalRecord框架之基本使用(一)

    Deleting Entities - 删除实体

    要在默认上下文中删除单个实体:

    [myPerson MR_deleteEntity];
    

    从指定上下文中删除实体:

    [myPerson MR_deleteEntityInContext:otherContext];
    

    获取默认上下文中的所有实体:

    [Person MR_truncateAll];
    

    获取指定上下文中所有实体:

    [Person MR_truncateAllInContext:otherContext];
    

    Fetching Entities - 获取实体

    1. Basic Finding - 基本查找

    MagicalRecord中的大多数方法都返回NSArray结果。

    例如,如果您有一个名为Person的实体与Department实体相关(如 Apple's Core Data examples中所示),您可以使用以下方法从持久性存储中检索所有Person实体:

    NSArray *people = [Person MR_findAll];
    

    要返回按特定属性排序的相同实体:

    NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName"
                                             ascending:YES];
    

    要返回按多个属性排序的实体:

    NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName"
                                             ascending:YES];
    

    返回由具有不同值的多个属性排序的结果。 如果您没有为任何属性提供值,它将默认为您在模型中设置的任何值:

    NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName:NO,FirstName"
                                             ascending:YES];
    
    // OR
    
    NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName:YES"
                                             ascending:NO];
    

    如果您有一种从数据存储中检索单个对象的唯一方法(例如标识符属性),则可以使用以下方法:

    Person *person = [Person MR_findFirstByAttribute:@"FirstName"
                                           withValue:@"Forrest"];
    

    2. Advanced Finding - 高级查找

    如果您想更具体地使用搜索,可以使用谓词:

    NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", @[dept1, dept2]];
    NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];
    

    3. Returning an NSFetchRequest - 返回一个NSFetchRequest

    NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];
    NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];
    

    对于这些单行调用中的每一个,都会创建任何排序条件的NSFetchRequestNSSortDescriptors

    4. Customizing the Request - 自定义请求

    NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];
    
    NSFetchRequest *peopleRequest = [Person MR_requestAllWithPredicate:peopleFilter];
    [peopleRequest setReturnsDistinctResults:NO];
    [peopleRequest setReturnPropertiesNamed:@[@"FirstName", @"LastName"]];
    
    NSArray *people = [Person MR_executeFetchRequest:peopleRequest];
    

    5. Find the number of entities - 查找实体数量

    您还可以在持久性存储中执行特定类型的所有实体的计数:

    NSNumber *count = [Person MR_numberOfEntities];
    

    或者,如果您正在寻找基于谓词或某些过滤器的实体计数:

    NSNumber *count = [Person MR_numberOfEntitiesWithPredicate:...];
    

    还有一些补充方法可以返回NSUInteger而不是NSNumber实例:

    + (NSUInteger) MR_countOfEntities;
    + (NSUInteger) MR_countOfEntitiesWithContext:(NSManagedObjectContext *)context;
    + (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter;
    + (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter
                                         inContext:(NSManagedObjectContext *)context;
    

    6. Aggregate Operations - 聚合操作

    NSNumber *totalCalories = [CTFoodDiaryEntry MR_aggregateOperation:@"sum:"
                                                          onAttribute:@"calories"
                                                        withPredicate:predicate];
    
    NSNumber *mostCalories  = [CTFoodDiaryEntry MR_aggregateOperation:@"max:"
                                                          onAttribute:@"calories"
                                                        withPredicate:predicate];
    
    NSArray *caloriesByMonth = [CTFoodDiaryEntry MR_aggregateOperation:@"sum:"
                                                           onAttribute:@"calories"
                                                         withPredicate:predicate
                                                               groupBy:@"month"];
    

    7. Finding entities in a specific context - 在指定上下文中查找实体

    所有findfetchrequest方法都有一个inContext:方法参数,允许您指定要查询的managed object context

    NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext];
    
    Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName"
                                                      withValue:@"Gump"
                                                      inContext:someOtherContext];
    
    NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];
    

    Saving Entities - 保存实体

    1. When should I save? - 何时使用

    通常,当数据发生变化时,您的应用应该保存到持久存储中。 有些应用程序选择在应用程序终止时存储,但在大多数情况下这不是必需的 - 事实上,如果您只是在应用程序终止时保存,那么您将面临数据丢失的风险! 如果你的应用程序崩溃会怎么样? 用户将失去他们所做的所有改变 - 这是一种糟糕的经历,并且很容易避免。

    如果您发现保存需要很长时间,那么您应该考虑做以下几件事:

    • 1) Save in a background thread - 在后台线程中保存MagicalRecord提供了一个简单,干净的API,用于对实体进行更改,然后将它们保存在后台线程中 - 例如:
    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    
        // Do your work to be saved here, against the `localContext` instance
        // Everything you do in this block will occur on a background thread
    
    } completion:^(BOOL success, NSError *error) {
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    
    • 2)Break the task down into smaller saves - 将任务分解为较小的保存:像导入大量数据这样的任务应该总是分解成更小的块。 对于您应该一次性保存多少数据没有一刀切的规则,因此您需要使用Apple's Instruments等工具来衡量应用程序的性能并适当调整。

    2. Handling Long-running Saves - 处理长时间运行的保存

    On iOS

    当应用程序在iOS上终止时,会给它一个小的机会窗口来整理并将任何数据保存到磁盘。 如果您知道保存操作可能需要一段时间,最好的方法是请求延长应用程序的到期时间,如下所示:

    UIApplication *application = [UIApplication sharedApplication];
    
    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    
    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    
        // Do your work to be saved here
    
    } completion:^(BOOL success, NSError *error) {
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    

    请务必仔细阅读read the documentation for beginBackgroundTaskWithExpirationHandler的文档,因为不恰当或不必要地延长应用程序的生命周期可能会使您的应用程序被App Store拒绝。

    On OS X

    OS X Mavericks(10.9)及更高版本中,App Nap可以使您的应用程序在后台运行时有效终止。 如果您知道保存操作可能需要一段时间,最好的方法是暂时禁用自动和突然终止(假设您的应用支持这些功能):

    NSProcessInfo *processInfo = [NSProcessInfo processInfo];
    
    [processInfo disableSuddenTermination];
    [processInfo disableAutomaticTermination:@"Application is currently saving to persistent store"];
    
    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    
        // Do your work to be saved here
    
    } completion:^(BOOL success, NSError *error) {
        [processInfo enableSuddenTermination];
        [processInfo enableAutomaticTermination:@"Application has finished saving to the persistent store"];
    }];
    

    与iOS方法一样,在应用程序中实现此方法之前,请务必阅读read the documentation on NSProcessInfo


    Importing Data - 引入数据

    MagicalRecord可以帮助将标准NSObject实例(如NSArrayNSDictionary)中的数据直接导入到Core Data存储中。

    使用MagicalRecord将数据从外部源导入持久性存储是一个两步过程:

    • 1) 使用您的数据模型定义您导入的数据如何映射到您的存储(它几乎没有代码!)
    • 2) 执行数据导入

    1. Define Your Import - 定义你的引入

    来自外部来源的数据在质量和结构上可能会发生巨大变化,因此我们已尽最大努力使MagicalRecord的导入流程变得灵活。

    MagicalRecord可以从任何符合键值编码(KVC)的对象导入数据。 我们通常会发现人们使用NSArrayNSDictionary实例,但它适用于任何符合KVC标准的NSObject子类。

    MagicalRecord利用Xcode数据建模工具的User Info值,允许配置导入选项和映射,而无需编辑任何代码。

    供参考:用户信息键和值保存在NSDictionary中,该NSDictionary附加到数据模型中的每个实体,属性和关系,并且可以通过NSEntityDescription实例上的userInfo方法访问。

    Xcode的数据建模工具使您可以通过数据模型检查器的User Info组访问该字典。 编辑数据模型时,可以使用Xcode的菜单 - View > Utilities > Show Data Model Inspector或按键盘上的⌥⌘3

    默认情况下,MagicalRecord将自动尝试将属性和关系名称与导入数据中的键匹配。如果模型中的属性或关系名称与数据中的键匹配,则无需执行任何操作 - 将自动导入附加到键的值。

    例如,如果实体上的属性名称为'firstName',则MagicalRecord将假定要导入的数据中的键也将具有'firstName'键 - 如果是,则实体的firstName属性将设置为该值您的数据中的firstName键。

    通常,您导入的数据中的键和结构与实体的属性和关系不匹配。在这种情况下,您需要告诉MagicalRecord如何将导入数据的键映射到数据模型中的正确属性或关系。

    我们在Core Data中处理的三个关键对象中的每一个 - Entities, Attributes and Relationships - 都有可能需要通过user info键指定的选项:

    Attributes

    Entities

    Relationships

    2. Importing Objects - 引入对象

    要使用MagicalRecord将数据导入你的存储,您需要了解两件事:

    • 1) 您要导入的数据的格式以及它的方式

    MagicalRecord导入背后的基本思想是你知道应该导入数据的实体,然后你编写一行代码将这个实体与要导入的数据联系起来。 有几个选项可以启动导入过程。

    要从对象自动创建新实例,可以使用以下更短的方法:

    NSDictionary *contactInfo = // Result from JSON parser or some other source
    
    Person *importedPerson = [Person MR_importFromObject:contactInfo];
    

    您还可以使用两阶段方法:

    NSDictionary *contactInfo = // Result from JSON parser or some other source
    
    Person *person = [Person MR_createEntity]; // This doesn't have to be a new entity
    [person MR_importValuesForKeysWithObject:contactInfo];
    

    如果您希望通过覆盖其属性来更新现有对象,则两步方法可能会有所帮助。

    + MR_importFromObject:将根据配置的查找值查找现有对象(请参阅relatedByAttributeattributeNameID)。 还要注意这是如何遵循在Cocoa中导入键值对列表的内置范例,以及遵循导入数据的安全方式。

    + MR_importFromObject:类方法使用前面提到的-MR_importValuesForKeysWithObject:实例方法提供创建新对象的包装器,并返回填充了数据的新创建的对象。

    值得注意的一个关键项是这两种方法都是同步的。 虽然某些导入将比其他导入更长时间,但仍然强烈建议在后台执行所有导入,以免影响用户交互。 如前所述,MagicalRecord提供了一个方便的API,使后台线程更易于管理:

    [MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext {
      Person *importedPerson = [Person MR_importFromObject:personRecord inContext:localContext];
    }];
    

    3. Importing Arrays - 导入数组

    通常使用JSON数组提供数据列表,或者导入单个类型数据的大型列表。 导入此类列表的详细信息将在+ MR_importFromArray:类方法中进行处理。

    NSArray *arrayOfPeopleData = /// result from JSON parser
    NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];
    

    此方法与+ MR_importFromObject:也是同步的,因此对于后台导入,请使用前面提到的辅助方法在后台执行block块。

    如果您的导入数据与您的Core Data模型完全匹配,那么请不要继续读取,因为您需要将上述方法导入Core Data存储中。 但是,如果您的数据与大多数数据一样,没有什么微小的偏差,请继续读取,因为我们将介绍MagicalRecord的一些功能,这些功能将帮助您处理几个常见的偏差。

    4. Best Practice - 最佳实践

    Handling Bad Data When Importing - 导入时处理错误数据

    API通常可以返回格式或值不一致的数据。 处理此问题的最佳方法是在实体类上使用导入类别方法。 提供了三个:

    通常,如果您的数据不好,您将需要修复导入在尝试导入任何值后所执行的操作。

    常见的情况是导入JSON数据,其中数字字符串通常可能被误解为实际数字。 如果要确保将值作为字符串导入,可以执行以下操作:

    @interface MyGreatEntity
    
    @property(readwrite, nonatomic, copy) NSString *identifier;
    
    @end
    
    @implementation MyGreatEntity
    
    @dynamic identifier;
    
    - (void)didImport:(id)data
    {
      if (NO == [data isKindOfClass:[NSDictionary class]]) {
        return;
      }
    
      NSDictionary *dataDictionary = (NSDictionary *)data;
    
      id identifierValue = dataDictionary[@"my_identifier"];
    
      if ([identifierValue isKindOfClass:[NSNumber class]]) {
        NSNumber *numberValue = (NSNumber *)identifierValue;
    
        self.identifier = [numberValue stringValue];
      }
    }
    
    @end
    

    Deleting local records on import update - 在导入更新时删除本地记录

    有时,您需要确保后续导入操作不仅会更新,还会删除未包含在远程数据集中的本地记录。 为此,请通过relatedByAttribute(下例中的id)获取此更新中未包含的所有本地记录,并在导入新数据集之前立即删除它们。

    NSArray *arrayOfPeopleData = /// result from JSON parser
    NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];
    NSArray *idList = [arrayOfPeopleData valueForKey:@"id"];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(id IN %@)", idList];
    [Person MR_deleteAllMatchingPredicate:predicate];
    

    如果您还想确保在此更新期间删除相关记录,您可以使用与上面类似的逻辑,但在PersonisImport:方法中实现它

    @implementation Person
    
    -(void)willImport:(id)data {
        NSArray *idList = [data[@"posts"] valueForKey:@"id"];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(id IN %@) AND person.id == %@", idList, self.id];
        [Post MR_deleteAllMatchingPredicate:predicate];
    }
    

    如果您还想确保在此更新期间删除相关记录,您可以使用与上面类似的逻辑,但在PersonisImport:方法中实现它

    @implementation Person
    
    -(void)willImport:(id)data {
        NSArray *idList = [data[@"posts"] valueForKey:@"id"];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(id IN %@) AND person.id == %@", idList, self.id];
        [Post MR_deleteAllMatchingPredicate:predicate];
    }
    

    Logging - 日志

    MagicalRecord在与Core Data的大多数交互中都内置了日志记录。 在获取或保存数据期间发生错误时,会捕获这些错误并(如果已启用它们)记录到控制台。

    日志记录配置为在调试版本中默认输出调试消息(MagicalRecordLoggingLevelDebug),并将在发布版本中输出错误消息(MagicalRecordLoggingLevelError)

    可以通过调用[MagicalRecord setLoggingLevel:]来配置记录,使用一个预定义的日志记录级别:

    • MagicalRecordLogLevelOff:不记录任何内容
    • MagicalRecordLoggingLevelError:记录所有错误
    • MagicalRecordLoggingLevelWarn:记录警告和错误
    • MagicalRecordLoggingLevelInfo:记录信息,警告和错误消息
    • MagicalRecordLoggingLevelDebug:记录所有调试,信息,警告和错误消息
    • MagicalRecordLoggingLevelVerbose:记录详细的诊断,信息,警告和错误消息

    日志记录级别默认为MagicalRecordLoggingLevelWarn

    1. CocoaLumberjack

    如果它可用,MagicalRecord会将其日志指向CocoaLumberjack。 您需要做的就是确保在导入MagicalRecord之前导入了CocoaLumberjack,如下所示:

    // Objective-C
    #import <CocoaLumberjack/CocoaLumberjack.h>
    #import <MagicalRecord/MagicalRecord.h>
    
    // Swift
    import CocoaLumberjack
    import MagicalRecord
    

    2. Disabling Logging Completely - 完全禁用日志记录

    对大多数人来说,这是不必要的。 将日志记录级别设置为MagicalRecordLogLevelOff将确保不打印日志。

    即使使用MagicalRecordLogLevelOff,也可以在进行日志调用时执行非常快速的检查。 如果您绝对需要禁用日志记录,则需要在编译MagicalRecord时定义以下内容:

    #define MR_LOGGING_DISABLED 1
    

    请注意,这只有在您将MagicalRecord的源添加到您自己的项目中时才有效。 您也可以将其添加到MagicalRecord项目的OTHER_CFLAGS中,作为-DMR_LOGGING_DISABLED = 1

    其他资源

    后记

    本篇主要讲述了MagicalRecord框架之基本使用,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

        本文标题:Core Data详细解析(十一) —— MagicalReco

        本文链接:https://www.haomeiwen.com/subject/bhfboftx.html