优秀的 CoreData 便捷存取框架,MagicalRecord

MagicalRecord是受Ruby on Rails 中 Active Record fetching便捷性的启发而成的库 。MagicalRecord的目标是:

精简 CoreData 相关代码

可以清晰、简单、单行获取数据

当需要对请求进行优化的时候,仍可以修改NSFetchRequest

文档

安装

添加MagicalRecord很简单,你可以从下列方式中任选其一:

使用 Carthage 安装

1.在项目文件夹内创建文件Cartfile,并添加以下内容:github "MagicalPanda/MagicalRecord"

2.打开终端进入项目目录,运行命令

carthage update

3.将Carthage/Build/目录下的 MagicalRecord.framework 拖拽到需要项目中。

使用 CocoaPods 安装

使用CocoaPods将 MagicalRecord 也非常简便:

向 Podfile 中添加一下内容:a.纯净安装

pod "MagicalRecord"

b.使用 CocoaLumberjack 纪录日志

pod "MagicalRecord/CocoaLumberjack"

2.进入项目目录,运行

pod update

3.向项目源文件添加 #import就可以开始使用了。

使用 Xcode 子项目

Xcode 子项目可以将MagicalRecord 作为内在依赖使用。

1.将Magicalrecord作为 Git 子模型(submodule)添加到项目中,

$ cd MyXcodeProjectFolder

$ git submodule add https://github.com/magicalpanda/MagicalRecord.git Vendor/MagicalRecord

$ git commit -m "Add MagicalRecord submodule"

2.将 Vendor/MagicalRecord/ 目录下的 MagicalRecord.xcproj 拖拽到你的项目中 3.点击左侧项目栏 ,target 栏下选择需要添加 MagicalRecord 的项目 4.选择 Build Phases 项并展开 Link Binary With Libraries 栏 5.点击 + 号添加对应平台的MagicalRecord framework 6.现在在你的目标源文件中,可以通过 #import添加使用 MagicalRecord。

小贴士

由于iOS平台 UIKit 不包含 Core Data,如果你在Xcode设置Link Frameworks Automatically 为 NO,你需要手动添加 CoreData.framework。如果是 OS X 平台,Core Data 已包含在 Cocoa 中,无需再手动添加。

分类方法的简略表达方式

MagicalRecord 提供的分类方法,均以 MR_作前缀。这遵循为防止命名冲突,苹果建议为分类方法添加前缀。

如果你想使用无前缀的分类方法,可以导入以下头文件:

如果你使用的是Swift语言,你需要将头文件导入到target的 Objective-C bridging header(桥接头文件)中。

导入头文件之后,在使用MagicalRecord之前,还需要先调用类方法+[MagicalRecord enableShorthandMethods]:

- (void)theMethodWhereYouSetupMagicalRecord

{

[MagicalRecord enableShorthandMethods];

// 正常设置 MagicalRecord

}

请注意,我们不提供此专题的技术支持。如果无法使用,请提交错误,我们将尽快修复。

开始使用

在工程的 PCH预编译头文件中导入 MagicalRecord.h 头文件,既可全局使用。

如果你使用的是 CocoaPods 或者 MagicalRecord.framework,导入代码如下:

每次调用都会实例化一块 Core Data 栈,并且对该实例提供 getter 和 setter 方法。 MagicalRecord 将这些实例作为默认使用的栈。

当在DEBUG模式下使用默认SQLite 数据存储时,如果没有创建新数据模型就更改了数据模型,MagicalRecord 将会自动删除旧的存储并创建新的存储。这将为你节省大量时间——每当更改数据模型,不必再卸载、重装应用。但请确保发布的应用的不是DEBUG版本:否则在未告知用户的情况下删除应用数据,这可不是好事!

在应用退出之前,需要调用 +cleanUp 方法:

[MagicalRecord cleanUp];

此方法将进行清理工作,清理自定义的错误处理,同时将MagicalRecord 创建的 CoreData 栈设为nil。

启用 iCloud 持久化存储

要使用苹果 iCloud Core Data 同步存储数据,使用下列方法替代上一小节中的方法:

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

localStoreNamed:(NSString *)localStore;

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

contentNameKey:(NSString *)contentNameKey

localStoreNamed:(NSString *)localStoreName

cloudStorePathComponent:(NSString *)pathSubcomponent;

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

contentNameKey:(NSString *)contentNameKey

localStoreNamed:(NSString *)localStoreName

cloudStorePathComponent:(NSString *)pathSubcomponent

completion:(void (^)(void))completion;

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

localStoreAtURL:(NSURL *)storeURL;

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

contentNameKey:(NSString *)contentNameKey

localStoreAtURL:(NSURL *)storeURL

cloudStorePathComponent:(NSString *)pathSubcomponent;

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

contentNameKey:(NSString *)contentNameKey

localStoreAtURL:(NSURL *)storeURL

cloudStorePathComponent:(NSString *)pathSubcomponent

completion:(void (^)(void))completion;

想了解更多内容,请查看Apple’s “iCloud Programming Guide for Core Data”。

提示

如果你需要同时管理多个iCloud 存储内容,我们推荐使用可以设置 contentNameKey 的方法。不能设置contentNameKey的方法则会根据应用的 bundle identifier(CFBundleIdentifier)自动生成 NSPersistentStoreUbiquitousContentNameKey作为标示。

使用托管对象上下文(managed object Context)

创建新的托管对象上下文

MagicalRecord提供了很多便捷的类方法,帮助你创建托管对象上下文(managed object context):

+ [NSManagedObjectContext MR_newContext]: 将默认上下文设为父上下文,其并发类型为 NSPrivateQueueConcurrencyType

+ [NSManagedObjectContext MR_newMainQueueContext]: 并发类型为NSMainQueueConcurrencyType

+ [NSManagedObjectContext MR_newPrivateQueueContext]: 并发类型为NSPrivateQueueConcurrencyType.

+ [NSManagedObjectContext MR_newContextWithParent:…]: 可以设置父上下文,并发类型 NSPrivateQueueConcurrencyType.

+ [NSManagedObjectContext MR_newContextWithStoreCoordinator:…]: 可以为新上下文设置持久化存储协调器(persistent store coordinator),并发类型 NSPrivateQueueConcurrencyType.

默认上下文(Default Context)

使用 Core Data 时,经常需要处理 NSManagedObject 和 NSManagedObjectContext 对象。

MagicalRecord 提供了便捷的类方法获取默认托管对象上下文NSManagedObjectContext。获取的上下文在主线程中执行操作,并可贯穿整个app进行操作,非常适合单线程的简单应用。

获取方法:NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];

通过 MagicalRecord ,这个上下文可以在任何有需要的方法内使用。但是这些方法不能使用上下文参数来指定特定上下文。

如果你需要一个不在主线程执行的上下文,使用此方法:

NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_newContext];

此方法得到的上下文与默认上下文具有相同的数据结构(object model)和持久化存储(persistent store),并安全的在其他线程中执行。同时,它的父上下文(parent context)为默认上下文。

如果你想在后台上下文 myNewContext中执行所有获取请求(fetch requests),使用:[NSManagedObjectContext MR_setDefaultContext:myNewContext];

注意:我们强烈建议在主线程中创建并设置默认上下文,同时将默认上下文的类型设置为NSMainQueueConcurrencyType。

在后台线程中执行任务

MagicalRecord 提供了创建及使用后台线程上下文的方法。后台保存操作为代码块的形式,和 UIView 动画方法类似,但也有细微区别:

更改实体的块不会在主线程中执行

代码块只提供一个 NSManagedObjectContext 对象

举个例子,假如我们有一个名为Person 的实体,需要同时设置它的属性 firstName 和 lastName, 我们可以使用 MagicalRecord 创建后台上下文并进行保存操作:

Person *person = ...;

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

Person *localPerson = [person MR_inContext:localContext];

localPerson.firstName = @"John";

localPerson.lastName = @"Appleseed";

}];

这个方法中,block 将为你提供合适的上下文来执行操作,所以你不必再考虑如何设置上下文了。当提供的上下文完成操作后,将通知默认上下文已更改了实体,并进行更新操作。

如果你想在“保存块”(save block)执行完成之后进行其他动作,可以使用“完成块” completion block:

Person *person = ...;

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

Person *localPerson = [person MR_inContext:localContext];

localPerson.firstName = @"John";

localPerson.lastName = @"Appleseed";

} completion:^(BOOL success, NSError *error) {

self.everyoneInTheDepartment = [Person findAll];

}];

colpletion block 在主线程(队列)中执行,所以UI 相关的操作可以放在这个块中。

创建实体对象

创建并插入新的实体实例到默认上下文(default context)中:

Person *myPerson = [Person MR_createEntity];

创建并将实体插入到指定上下文中:

Person *myPerson = [Person MR_createEntityInContext:otherContext];

删除实体对象

删除默认上下文中实体:

[myPerson MR_deleteEntity];

删除指定上下文中实体:

[myPerson MR_deleteEntityInContext:otherContext];

删除默认上下文中所有实体:

[Person MR_truncateAll];

删除指定上下文中所有实体:

[Person MR_truncateAllInContext:otherContext];

获取实体对象

基础查询

MagicalRecord 中大多数查询方法返回 NSArray 类型的结果。

假设我们有一个名为 Person 的实体,且与实体 Department 相关联。从持久化存储(persistent store)中查询所有 Person 实体:

NSArray *people = [Person MR_findAll]; //

查询所有Person实体,按照指定属性排序:

//按照属性 LastName 升序(asceding)排序

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

查询所有Person实体,按照多个属性排序:

//按照属性 LastName 升序,FirstName 升序排序

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

查询所有Person实体,按照多个属性排序,指定的多个属性可以设置不同的排序方式,如果某个属性未指明排序方式,则按方法中”ascending:”参数进行排序:

//按照属性LastName降序、FirstName升序排序

NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName:NO,FirstName"

ascending:YES];

// 等效于

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

ascending:NO];

查询特定属性值的实体,使用此方法:

//所有 FirstName 为 Forrest 的实体

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

高级查询

使用 谓词 查询特定实体:

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

NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];

获取 NSFetchRequest 对象

使用谓词获取对应查询条件的 NSFetchRequest 对象

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

NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];

自定义查询

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];

查询实体数目

你还可以获取指定类型的实体个数:

//返回类型为NSNumber

NSNumber *count = [Person MR_numberOfEntities];

使用谓词或其它过滤器查询实体数目:

//返回类型为NSNumber

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

以下方法返回类型则是NSUInteger:

+ (NSUInteger) MR_countOfEntities;

+ (NSUInteger) MR_countOfEntitiesWithContext:(NSManagedObjectContext *)context;

+ (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter;

+ (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter

inContext:(NSManagedObjectContext *)context;

合计操作(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"];

在指定上下文查找

所有的 find, fetch, request 方法都可以通过 inContext: 参数指定查询的上下文:

NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext];

Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName"

withValue:@"Gump"

inContext:someOtherContext];

NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];

保存实体

保存操作的时机

大多数情况下,当数据发生变化的时候就执行保存操作。有一些应用只是在应用关闭的时候才保存,但这样增加了丢失数据的风险。当应用崩溃的时候就会造成数据丢失,用户会丢失他们的数据。应当避免这种情况的发生。

如果保存操作花费的时间太长,你可以采取以下措施:

1.在后台线程中执行保存操作:

MagicalRecord 为更改、保存实体提供了简洁的API。

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {

// Do your work to be saved here, against the `localContext` instance

// 在此块中的所有操作均在后台执行

} completion:^(BOOL success, NSError *error) {

[application endBackgroundTask:bgTask];

bgTask = UIBackgroundTaskInvalid;

}];

2.将任务拆分成小任务保存:

像一次性导入大量数据的任务,你需要将数据拆分成小块来操作。一次处理的数据大小并没有统一标准,你需要使用类似 Apples’ Instruments 的工具来测试调整,获取最佳大小。

处理长时储存

iOS平台

当iOS平台app退出时,app会获得短暂时间向硬盘中保存整理数据。如果你的应用存储时间较长,最好在应用完全终止前,请求延长后台执行时间。

UIApplication *application = [UIApplication sharedApplication];

__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{

[application endBackgroundTask:bgTask];

bgTask = UIBackgroundTaskInvalid;

}];

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {

// 此处执行保存操作

} completion:^(BOOL success, NSError *error) {

[application endBackgroundTask:bgTask];

bgTask = UIBackgroundTaskInvalid;

}];

请确保认真阅读beginBackgroundTaskWithExpirationHandler ,因为不适当或不必要的延长应用生命周期,申请上架的时候可能会被 App Store 拒绝。

OS X 平台

OS X Mavericks (10.9) 及更高版本中,App Nap 可以让你 app 看起来像关闭了,但仍在后台运行。如果有长时间的保存操作,最好的方式是禁用自动终止和突然终止( automatic and sudden termination):

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方法相同,使用前请仔细阅读 《NSProcessInfo文档》 。

MagicalRecord 2.3.0 版本变化

不建议使用 Context For Current Thread

早期发布的MagicalRecord中,我们提供了获取当前线程中数据上下文(managed object context )的方法。不幸的是,这个方法已经不能正确获取当前线程中的上下文了。 Grand Central Dispatch (GCD) 导致一个队列(queue)可能被分发到多个线程中,而CoreData 使用 GCD 之前,旧方法中使用的是旧的 NSThread API 。如果想了解更多内容,请看 Saul 的文章Why contextForCurrentThread Doesn’t Work in MagicalRecord 。

为了兼容旧版本,2.3.0 版本中仍然有 +MR_contextForCurrentThread 方法。但我们不推荐使用。

特别是不要再+[MagicalRecord saveWithBlock:…] 方法内使用 +MR_contextForCurrentThread 方法——返回的上下文很可能是错的!

如果你现在仍然想使用旧方法,请使用可以接受上下文参数的变体,并将 +[MagicalRecord saveWithBlock:…]中块的上下文参数(context)作为它的参数。

废弃方法:

[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {

NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createEntity];

// …

}];

替代方法:

[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {

NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createEntityInContext:localContext];

// …

}];

当MagicalRecord 更新到 3.0 版,获取当前线程上下文的方法会被完全移除。未设置“上下文(context)”的方法,将使用默认上下文——详情见 MagicalRecord 3.0 版文档。

MagicalRecord 2.2.0 版本变化

在 MagicalRecord 2.2.0版中, 保存相关的 API 修改的更为统一,同时也更符合Core Data 中的命名方式。同时加入了大量自动测试来确保保存方法,无论新旧,在更新后仍可以使用。

MR_save 方法暂时恢复为在当前线程同步运行,同时保存到持久化存储中。但在下一个主要版本 (MagicalRecord 3.0)中,将会移除MR_save 方法。如果你和在未来版本的库保持一致,现在应使用 MR_saveToPersistentStoreAndWait 方法。

新方法:

下列为新添加的方法:

NSManagedObjectContext+MagicalSaves

- (void) MR_saveOnlySelfWithCompletion:(MRSaveCompletionHandler)completion;

- (void) MR_saveToPersistentStoreWithCompletion:(MRSaveCompletionHandler)completion;

- (void) MR_saveOnlySelfAndWait;

- (void) MR_saveToPersistentStoreAndWait;

- (void) MR_saveWithOptions:(MRSaveContextOptions)mask completion:(MRSaveCompletionHandler)completion;

MagicalRecord+Actions

+ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block;

+ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;

+ (void) saveWithBlockAndWait:(void(^)(NSManagedObjectContext *localContext))block;

+ (void) saveUsingCurrentThreadContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;

+ (void) saveUsingCurrentThreadContextWithBlockAndWait:(void (^)(NSManagedObjectContext *localContext))block;

弃用方法

新方法将替代以下方法,下列方法将在 MagicalRecord 3.0 中移除:

NSManagedObjectContext+MagicalSaves

- (void) MR_save;

- (void) MR_saveWithErrorCallback:(void(^)(NSError *error))errorCallback;

- (void) MR_saveInBackgroundCompletion:(void (^)(void))completion;

- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback;

- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion;

- (void) MR_saveNestedContexts;

- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback;

- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion;

MagicalRecord+Actions

+ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block;

+ (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block;

+ (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(void(^)(void))completion;

+ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *error))errorHandler;

导入数据(importing data)

我们正在着手更新文档,更新期间请参考 Cocoa Is My Girlfriend 中的文章 Importing Data Made Easy 。本节大部分内容是参考 Saul 的文章撰写的。

MagicalRecord 可以直接从标准 NSObject 对象(NSArray、NSDictionary等)导入到Core Data 存储。

使用MagicalRecord 将数据从外部资源导入到持久化存储中,需要两步工作:

1.定义导入的数据与持久化存储之间的映射关系

2.执行导入数据操作

定义导入映射

外部数据源在质量(quality)和结构(structure)上千差万别,我们尽可能让 MagicalRecord 能够应对各种情形。

MagicalRecord 可以从任何遵守键值编码(KVC)的对象导入数据。 大家通常只使用 NSArray 和 NSDictionary 对象作为数据源,实际上MagicalRecord 适用于任何兼容KVC的 NSObject 子类。

MagicalRecord 利用 Xcode数据建模工具“User Info” 的值,无需更改代码即可配置导入选项(import option)和映射(mapping)。

供参考:user info 的键值以字典(NSDictionary)的形式附加到数据模型的实体(entity)、属性(attribute)和关系(relationship)中,可以通过 NSEntityDescription 对象的 userInfo 方法对其访问。

在 Xcode 数据建模工具的帮助下,可以直接通过数据模型(Data Model)的 Inspector 窗口中的“User Info”更改这个字典。编辑数据模型时,可以通过 View > Utilities > Show Data Model Inspector 或按 ⌥+⌘+3 打开 Inspector 窗口。

默认情况下,MagicalRecord 会根据导入数据的键(key)的名称,自动匹配实体(entity)的属性(attribute)和关系(relationships)。如果数据模型的键(key)与属性、关系名称相匹配,那么你不需要做任何操作——MagicalRecord便会自动导入该键对应的值(value)。

举个例子,实体有名为 ‘firstName’ 的属性(attribute),MagicalRecord将假定导入的数据中有同名的键(key)——如果同名键真的存在,那么将使用 firstName的值,来为实体firstName 属性赋值。

但多数情况下并没有那么理想,要导入的数据的键(key)和结构(structure)并不能匹配实体的属性和关系。此时就需要通过设置数据的键与实体的属性之间的映射关系来导入了。

Core Data 中我们需要处理的三个对象——实体, 属性和关系 ——需要通过user info key 来设置其参数:

Attributes

Key Type Purpose

attributeValueClassName String 待定(TBD)

dateFormat String 待定. 默认格式 yyyy-MM-dd'T'HH:mm:ssz.

mappedKeyName String 指明导入数据的keypath,支持多层级,以 .作为分隔符, 例如location.latitude

mappedKeyName.[0-9] String 备用keypath,当mappedKeyName指定的keypath不存在时使用。语法同上。

useDefaultValueWhenNotPresent Boolean 为true时,如果导入数据值为空,则为此属性设置默认值。

Entities

Key Type Purpose

relatedByAttribute String 指明连接两个实体的关系(relationship)的目标实体的属性(attribute)

Relationships

Key Type Purpose

mappedKeyName String 指明导入数据的keypath,支持多层级,以 .作为分隔符, 例如location.latitude

mappedKeyName.[0-9] String 备用keypath,当mappedKeyName指定的keypath不存在时使用。语法同上。

relatedByAttribute String 指明关系(relationship)的目标的属性(attribute)

type String TBD

导入对象

导入之前你应当充分了解导入数据对象的结构,同时构建与之对应的实体。完成这些之后,再进行数据导入。导入的方式有以下几种:

依据数据对象自动创建新实例:

NSDictionary *contactInfo = // 此处解析 JSON 或其他数据源

Person *importedPerson = [Person MR_importFromObject:contactInfo];

两步方法:

NSDictionary *contactInfo = // 此处解析 JSON 或其他数据源

Person *person = [Person MR_createEntity]; // 此处不一定是新建实体,也可使用已有实体

[person MR_importValuesForKeysWithObject:contactInfo];

两步方法中的实体可以是新建的,也可以是已存在的,两步方法可以帮你更新已存在的实体。

+MR_importFromObject:根据已配置的查询值(lookup value)查找相应对象(即上文中的relatedByAttribute 和 attributeNameID)。它遵循Cocoa导入key-value 的范例,保证安全导入数据。

+MR_importFromObject: 是对实例方法-MR_importValuesForKeysWithObject:和创建对象方法的封装,返回值为赋值的新对象。

以上两个方法均为同步执行方法。导入时间长短不一,很可能影响交互体验,若将所有数据导入工作都放在后台执行,就不会影响软件交互了。像前面提到的,MagicalRecord 提供了很多好用的后台线程 API :

[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext {

Person *importedPerson = [Person MR_importFromObject:personRecord inContext:localContext]; //导入操作

}];

导入数组

使用 array 对象来保存单一类型数据很常见,可以使用 +MR_importFromArray: 方法来处理:

NSArray *arrayOfPeopleData = /// 解析JSON的结果

NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];

此方法与+MR_importFromObject:方法类似,也是同步执行方法,所以你想要后台执行,就得使用前面提到的方法,在后台块里执行。

如果你导入的数据与Core Data model 完全匹配,那么上面的方法已经能帮你完成导入工作了。但大多数情况下并不理想,两者之间稍有差别, MagicalRecord 的一些方法处理导入数据和Core Data model之间的不同。

实践

错误数据处理

API 方法经常会返回格式错误或内容错误的数据。最好的处理方法是通过实体类的分类方法处理,有三个方法可供选择:

Method Purpose

- (BOOL) shouldImport; 在导入数据前调用此方法。如果返回为 NO,则取消向实体导入数据。

- (void) willImport:(id)data; 紧邻数据导入之前调用。

- (void) didImport:(id)data; 紧邻数据导入之后调用。

一般来说,如果你尝试导入所有数据时,发现一些损坏数据,你可能想修复它们。

常见的情景是在导入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

更新数据时删除本地记录

在后续的导入操作中,有时不仅需要更新记录,同时要删除本地存在、远程数据库中不存在的数据。此时需要先根据 relatedByAttribute来判断哪些记录不在远程数据库中,然后将其删除。

NSArray *arrayOfPeopleData = /// JSON 解析结果

NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];

NSArray *idList = [arrayOfPeopleData valueForKey:@"id"];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(id IN %@)", idList];

[Person MR_deleteAllMatchingPredicate:predicate];

如果你想在更新操作中确保相关记录已被删除,你可以使用与上面代码类似的逻辑,并在Person 的 willImport:方法中实现:

@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];

}

记录日志

MagicalRecord 已经在Core Data 大部分交互中内置了日志纪录。读取、保存数据时,如果启用了日志,错误信息将会被打印到控制台(console)。

在调试(debug)模式下,日志会打印调试信息(MagicalRecordLoggingLevelDebug),在发布(release)模式下,日志则会输出错误信息(MagicalRecordLoggingLevelError)。

可以通过调用[MagicalRecord setLoggingLevel:]方法来配置日志;选择使用以下几个日志等级:

MagicalRecordLogLevelOff: 关闭日志

MagicalRecordLoggingLevelError: 记录所有错误

MagicalRecordLoggingLevelWarn: 记录警告和错误信息

MagicalRecordLoggingLevelInfo: 记录有用信息,警告和错误信息

MagicalRecordLoggingLevelDebug: 记录所有调试、有用信息、警告和错误信息

MagicalRecordLoggingLevelVerbose: 记录诊断信息,有用信息、错误和警告

日志默认配置为MagicalRecordLoggingLevelWarn。

CocoaLumberjack

如果你的项目支持 CocoaLumberjack,MagicalRecord 将把日志交由CocoaLumberjack处理。你所需要做的就是在导入 MagicalRecord 之前导入 CocoaLumberjack,例如:

其他资料

以下为安装使用 MagicalRecord 的相关文章:

How to make Programming with Core Data Pleasant

Using Core Data with MagicalRecord

Super Happy Easy Fetching in Core Data

Core Data and Threads, without the Headache

Unit Testing with Core Data

技术支持

MagicalRecord is provided as-is, free of charge。如果需要技术支持,你可以选择以下途径:

在 Stackoverflow.com 提出你的问题并添加 MagicalRecord 标签。只有添加了 MagicalRecord 标签,核心团队才能看到你的问题。Stack Overflow 社区可以帮你迅速得到解答,,MagicalRecord is provided as-is, free of charge。 如果社区无法回答你的问题,那我们将尝试介入并回答你的提问。

如果你发现MagicalRecord中存在 bug, 请在 Github Issues page for MagicalRecord 页面提交 a support ticket 。我们将以最快速度解决。另外,请不要在 issue tracker 界面提一般问题。Support questions will be closed unanswered

对于个人问题或者需要紧急技术支持,可以 MagicalPanda 获取咨询服务

Twitter

关注 twitter @MagicalRecord 获取 MagicalRecord 最新信息。

开源地址:https://github.com/magicalpanda/MagicalRecord

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容