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

简介

在软件工程中,Active Record 是一种关系数据库存储数据的设计模式。在Martin Fowler2003年出版的《企业应用架构模式》一书中详细叙述并命名了本模式。 这些对象的接口包括插入、更新和删除,及增加与底层数据库表中列对应的属性。

Active Record是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。Active Record和Row Gateway十分相似,但前者是领域模型,后者是一种数据源模式关系型数据库往往通过外键来表述实体关系,Active Record在数据源层面上也将这种关系映射为对象的关联和聚集

-Wikipedia

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

  • 精简 CoreData 相关代码
  • 可以清晰、简单、单行获取数据
  • 当需要对请求进行优化的时候,仍可以修改NSFetchRequest

文档

安装

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

使用 Carthage 安装

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

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

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

使用 CocoaPods 安装

使用CocoaPods将 MagicalRecord 也非常简便:

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

b.使用 CocoaLumberjack 纪录日志

2.进入项目目录,运行

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

使用 Xcode 子项目

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

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

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

小贴士

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

分类方法的简略表达方式

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

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

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

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

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

开始使用

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

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

如果是直接将MagicalRecord文件直接添加到工程中,导入代码如下:

下一步,打开工程 AppDelegate.m,在 - applicationDidFinishLaunching: withOptions:-awakeFromNib 方法内,调用下列方法之一,设置Core Data栈:

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

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

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

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

启用 iCloud 持久化存储

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

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

提示

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

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

创建新的托管对象上下文

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

默认上下文(Default Context)

使用 Core Data 时,经常需要处理 NSManagedObjectNSManagedObjectContext 对象。

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

获取方法:

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

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

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

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

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

在后台线程中执行任务

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

  • 更改实体的块不会在主线程中执行
  • 代码块只提供一个 NSManagedObjectContext 对象

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

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

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

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

创建实体对象

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

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

删除实体对象

删除默认上下文中实体:

删除指定上下文中实体:

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

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

获取实体对象

基础查询

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

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

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

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

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

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

高级查询

使用 谓词 查询特定实体:

获取 NSFetchRequest 对象

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

自定义查询

查询实体数目

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

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

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

合计操作(Aggregate Operations)

在指定上下文查找

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

保存实体

保存操作的时机

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

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

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

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

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

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

处理长时储存

iOS平台

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

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

OS X 平台

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

与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 更新到 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

导入对象

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

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

两步方法:

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

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

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

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

导入数组

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

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

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

实践

错误数据处理

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

Method Purpose
- (BOOL) shouldImport; 在导入数据前调用此方法。如果返回为 NO,则取消向实体导入数据。
- (void) willImport:(id)data; 紧邻数据导入之前调用。
- (void) didImport:(id)data; 紧邻数据导入之后调用。

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

常见的情景是在导入JSON数据时,常常将数字字符串被错误的解析为数字。如果你想确保它们以字符串的类型导入,可以这么做:

更新数据时删除本地记录

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

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

记录日志

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

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

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

  • MagicalRecordLogLevelOff: 关闭日志
  • MagicalRecordLoggingLevelError: 记录所有错误
  • MagicalRecordLoggingLevelWarn: 记录警告和错误信息
  • MagicalRecordLoggingLevelInfo: 记录有用信息,警告和错误信息
  • MagicalRecordLoggingLevelDebug: 记录所有调试、有用信息、警告和错误信息
  • MagicalRecordLoggingLevelVerbose: 记录诊断信息,有用信息、错误和警告

日志默认配置为MagicalRecordLoggingLevelWarn。

CocoaLumberjack

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

彻底关闭日志(Logging)

大多数人可能并不需要关闭日志。但通过设置 MagicalRecordLogLevelOff ,可以确保不会打印任何日志信息。

当调用日志方法时,会迅速检查 MagicalRecordLogLevelOff来判断是否打印信息。如果确实需要禁用日志记录,当编译 MagicalRecord 时需要做以下定义:

请注意,上面的宏语句只适用于将源代码添加到工程中的情形。如果 MagicalRecord 是以工程的形式添加,那么需要在MagicalRecord工程中 将OTHER_CFLAGS 设为 -DMR_LOGGING_DISABLED=1.

其他资料

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

技术支持

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

1 4 收藏

资源整理者简介:张发白

简介还没来得及写 :) 个人主页 · 贡献了6个资源 · 1


直接登录

推荐关注

按分类快速查找

关于资源导航
  • 伯乐在线资源导航收录优秀的工具资源。内容覆盖开发、设计、产品和管理等IT互联网行业相关的领域。目前已经收录 1438 项工具资源。
    推送伯乐头条热点内容微信号:jobbole 分享干货的技术类微信号:iProgrammer