Core Data & MagicalRecord
iOS 本地数据持久化存储:
1、plist
2、归档
3、NSUserDefaults
4、NSFileManager
5、数据库
一、CoreData概述
CoreData是苹果自带的管理数据库的工具。使用Core Data有很多原因,其中最简单的一条就是:它能让你为Model层写的代码的行数减少为原来的50%到70%。 这归功于之前提到的Core Data的特性。更妙的是,对于上述特性你也既不用去测试,也不用花功夫去优化。
Core Data拥有成熟的代码,这些代码通过单元测试来保证品质。通过了几个版本的发布,已经被高度优化。 它能利用Model层的信息和运行时的特性,而不通过程序层的代码实现。 除了提供强大的安全支持和错误处理外,它还提供了最优的内存扩展性,可实现有竞争力的解决方案。不使用Core Data的话,你需要花很长时间来起草自己的方案,解决各种问题,这样做效率不高。
另外:CoreData主要是iOS对SQLite数据库的封装。CoreData有 对象-关系 的映射的功能,能把OC的对象存储成数据库或 xml 等。如果数据存储使用的是coreData,那么读取时可以不使用 SQLite 语句。
二、关于Core Data常见的误解
1、 Core Data不是一个关系型数据库,也不是关系型数据库管理系统(RDBMS)。
Core Data 为数据变更管理、对象存储、对象读取恢复的功能提供了支持。 它可以使用SQLite作为持久化存储的类型。 它本身并不是一个数据库(这点很重要,比如,你可以使用Core Data来记录数据变更,管理数据,但并不能用它向文件内存储数据)。
2 、它并不能取代你写代码的工作。虽然可以纯粹使用XCode的数据建模工具和Interface Builder来编写复杂程序,但在更多的程序中,你都自己动手写代码。
三、CoreData 的核心概念
关键的概念图
(1)NSManagedObjectModel 托管对象模型(MOM)
(用来加载Core Data数据模型文件,所有的数据模型可以全部加载到这个对象中。)
这个MOM由实体描述对象,即NSEntityDescription实例的集合组成,实体描述对象介绍见下面第7条。
作用:添加实体的属性,建立属性之间的关系
(2)NSManagedObjectContext 托管对象上下文(MOC)
(用于操作数据模型(对象),并检测数据模型(对象)的变化。)
在概念图2中,托管对象上下文(MOC)通过持久化存储协调器(PSC)从持久化存储(NSPersistentStore)中获取对象时,这些对象会形成一个临时副本在MOC中形成一个对象集合,该对象集合包含了对象以及对象彼此之间的一些关系。我们可以对这些副本进行修改,然后进行保存,然后MOC会通过 PSC 对 NSPersistentStore 进行操作,持久化存储就会发生变化。
CoreData中的NSManagedObjectModel 托管对象的数据模型(MOM),通过 MOC 进行注册。MOC有插入、删除以及更新数据模型等操作,并提供了撤销和重做的支持。
作用:插入数据,更新数据,删除数据
(3)NSPersistentStoreCoordinator 持久化存储协调器(PSC)
(数据持久化存储协调器,负责调度上层与底层对数据的操作。)
在应用程序和外部数据存储的对象之间提供访问通道的框架对象集合统称为持久化堆栈(persistence stack)。在堆栈顶部的是托管对象上下文(MOC),在堆栈底部的是持久化对象存储(persistent object stores)。在托管对象上下文和持久化对象存储之间便是持久化存储协调器(PSC)。应用程序通过类NSPersistentStoreCoordinator的实例访问持久化对象存储。
持久化存储协调器为一或多个托管对象上下文提供一个访问接口,使其下层的多个持久化存储可以表现为单一一个聚合存储。一个托管对象上下文可以基于持久化存储协调器下的所有数据存储来创建一个对象图。持久化存储协调器只能与一个托管对象模型(MOM)相关联。
(4)NSManagedObject 托管对象(MO)
(具体的数据模型对象。)
托管对象必须继承自NSManagedObject或者NSManagedObject的子类。NSManagedObject能够表述任何实体。它使用一个私有的内部存储,以维护其属性,并实现托管对象所需的所有基本行为。托管对象有一个指向实体描述的引用。NSEntityDescription 实体描述表述了实体的元数据,包括实体的名称,实体的属性和实体之间的关系。
(5)Controller 控制器
概念图1中绿色的 Array Controller,Object Controller,Tree Controller 这些控制器,一般都是通过 control+drag 将 Managed Object Context 绑定到它们,这样我们就可以在 nib 中可视化地操作数据
(6)NSFetchRequest 获取数据请求
使用托管对象上下文来检索数据时,会创建一个获取请求(fetch request)。类似Sql查询语句的功能。
(7)NSEntityDescription 实体描述
(模型描述类,能够实例化得到具体的数据模型对象。)
实体描述对象提供了一个实体的元数据,包括实体名(Name),类名(ClassName),属性(Properties)以及实体属性与其他实体的一些关系(Relationships)等。
(8).xcdatamodeld
里面是.xcdatamodeld文件,用数据模型编辑器编辑,编译后为.momd或.mom文件。
我们可以选中我们的应用程序(路径类似为/Users/Childhood/Library/Application Support/iPhone Simulator/7.1/Applications/005D926F-5763-4305-97FE-AE55FE7281A4),右键显示包内容,我们看到是这样的。
四、Core Data运作机制
1,应用程序先创建或读取模型文件(后缀为xcdatamodeld)生成 NSManagedObjectModel 对象。从模型文件(后缀为 xcdatamodeld)读取。
2,然后生成 NSManagedObjectContext 和 NSPersistentStoreCoordinator 对象,前者对用户透明地调用后者对数据文件进行读写。
3,NSPersistentStoreCoordinator 负责从数据文件(xml, sqlite,二进制文件等)中读取数据生成 Managed Object,或保存 Managed Object 写入数据文件。
4,NSManagedObjectContext 参与对数据进行各种操作的整个过程,它持有 Managed Object。我们通过它来监测 Managed Object。监测数据对象有两个作用:支持 undo/redo 以及数据绑定。这个类是最常被用到的。
5,Array Controller, Object Controller, Tree Controller 这些控制器一般与 NSManagedObjectContext 关联,因此我们可以通过它们在 nib 中可视化地操作数据对象。
五、代码步骤
1、加载Core Data文件
2、将数据的存储方式设定为数据库
3、操作数据:增加
4、操作数据:查询
5、操作数据:修改
6、操作数据:删除
MagicalRecord https://github.com/magicalpanda/MagicalRecord
注意: MagicalRecord 在 ARC 下运作,Core Data 是 ORM 方案,据说带来的麻烦比好处多,且 Core Data 建立的表没有主键,但对于对数据库没有性能要求,进行简单的数据操作完全够用,能简化无数的代码量.
MagicalRecord
In software engineering, the active record pattern is a design pattern found in software that stores its data in relational databases. It was named by Martin Fowler in his book Patterns of Enterprise Application Architecture. The interface to such an object would include functions such as Insert, Update, and Delete, plus properties that correspond more-or-less directly to the columns in the underlying database table.
在软件工程中,对象与数据库中的记录实时映射是一种设计模式,在处理关系型数据库的的软件中多有出现.这种设计模式被记录在 Martin Fowler 的<Patterns of Enterprise Application Architecture> 中.被操作对象的接口应该包含增删改查的方法,基本属性不多不少的刚好与数据库中的一条记录相对应.
Active record is an approach to accessing data in a database. A database table or view is wrapped into a class; thus an object instance is tied to a single row in the table. After creation of an object, a new row is added to the table upon save. Any object loaded gets its information from the database; when an object is updated, the corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for each column in the table or view.
实时映射记录是一种操作数据库的方式,一个数据库的表被封装成一个对象;这个对象中的一个实例会对应着该表中的一条记录.当创建一个对象时,一条记录也被插入到表中并保存起来.任何被加载的对象中的属性信息都从数据库中读取;当一个对象更新时,这个数据库表中对应的记录也会更新.这个被封装的类实现了实时操作的方法,且其属性一一对应于数据库中表的属性.
MagicalRecord was inspired by the ease of Ruby on Rails' Active Record fetching. The goals of this code are:
- Clean up my Core Data related code
- Allow for clear, simple, one-line fetches
- Still allow the modification of the NSFetchRequest when request optimizations are needed
MagicalRecord 灵感来自于简洁的Ruby语言中 Rails' Active Record 查询方式. MagicalRecord 这个开源库的核心思想是:
- 清除 Core Data 相关的代码
- 简洁的清除,简单的一行搜索记录的功能
- 当然允许使用NSFetchRequest,当存在着复杂的搜索条件时
拙劣的翻译请勿见怪,以下是我在最新的 Xcode 5.1 开 ARC 的环境下的使用教程.
1. 将 MagicalRecord 文件夹拖入到工程文件中,引入 CoreData.frame 框架
2. 在 .pch 文件中引入头文件 CoreData+MagicalRecord.h
注:只能在.pch文件中引头文件,否则无法通过编译
3. 创建 Model.xcdatamodeld 文件,并创建一个 Student 的 ENTITIES,最后创建出 Student 类
4. 在 Appdelete.m 文件中写以下代码
以下是增删改查的基本操作,但注意一点,在做任何的数据库操作之前,请先初始化以下,在Appdelete载入时初始化一次即可,否则找不到数据库而崩溃,你懂的.
//设置数据库名字
[MagicalRecord setupCoreDataStackWithStoreNamed:@"Model.sqlite"];
增加
增加一条记录
Student *person = [Student MR_createEntity];
person.name = @"Y.X.";
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
注意:创建了对象后是需要执行存储操作的
查询
查询所有的记录
NSArray *students = [Student MR_findAll];
根据某个属性某个条件查询
NSArray *students = [Student MR_findByAttribute:@"name" withValue:@"Y.X."];
根据排序取得搜索结果
NSArray *students = [Student MR_findAllSortedBy:@"name" ascending:YES];
我不一一列举了,查看一下头文件就知道了.
查询所有记录
+ (NSArray *) MR_findAll;
根据上下文句柄查询所有记录
+ (NSArray *) MR_findAllInContext:(NSManagedObjectContext *)context;
根据某个属性排序查询所有记录
+ (NSArray *) MR_findAllSortedBy:(NSString *)sortTerm ascending:(BOOL)ascending;
根据某个属性排序以及上下文操作句柄查询所有记录
+ (NSArray *) MR_findAllSortedBy:(NSString *)sortTerm ascending:(BOOL)ascending inContext:(NSManagedObjectContext *)context;
根据某个属性排序用谓词来查询记录
+ (NSArray *) MR_findAllSortedBy:(NSString *)sortTerm ascending:(BOOL)ascending withPredicate:(NSPredicate *)searchTerm;
根据某个属性排序以及上下文操作句柄用谓词来查询记录
+ (NSArray *) MR_findAllSortedBy:(NSString *)sortTerm ascending:(BOOL)ascending withPredicate:(NSPredicate *)searchTerm inContext:(NSManagedObjectContext *)context;
根据谓词查询
+ (NSArray *) MR_findAllWithPredicate:(NSPredicate *)searchTerm;
根据谓词以及上下文操作句柄来查询
+ (NSArray *) MR_findAllWithPredicate:(NSPredicate *)searchTerm inContext:(NSManagedObjectContext *)context;
以下都是查询一个对象时的操作,与上面重复,不一一赘述
+ (instancetype) MR_findFirst;
+ (instancetype) MR_findFirstInContext:(NSManagedObjectContext *)context;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm inContext:(NSManagedObjectContext *)context;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchterm sortedBy:(NSString *)property ascending:(BOOL)ascending;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchterm sortedBy:(NSString *)property ascending:(BOOL)ascending inContext:(NSManagedObjectContext *)context;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm andRetrieveAttributes:(NSArray *)attributes;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm andRetrieveAttributes:(NSArray *)attributes inContext:(NSManagedObjectContext *)context;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm sortedBy:(NSString *)sortBy ascending:(BOOL)ascending andRetrieveAttributes:(id)attributes, ...;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm sortedBy:(NSString *)sortBy ascending:(BOOL)ascending inContext:(NSManagedObjectContext *)context andRetrieveAttributes:(id)attributes, ...;
+ (instancetype) MR_findFirstByAttribute:(NSString *)attribute withValue:(id)searchValue;
+ (instancetype) MR_findFirstByAttribute:(NSString *)attribute withValue:(id)searchValue inContext:(NSManagedObjectContext *)context;
+ (instancetype) MR_findFirstOrderedByAttribute:(NSString *)attribute ascending:(BOOL)ascending;
+ (instancetype) MR_findFirstOrderedByAttribute:(NSString *)attribute ascending:(BOOL)ascending inContext:(NSManagedObjectContext *)context;
修改
NSArray *students = [Student MR_findByAttribute:@"name" withValue:@"Y.X."];
for (Student *tmp in students) {
tmp.name = @"Jane";
}
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
注意:既然要修改首先得需要找到记录,根据条件匹配找到记录,然后修改,然后保存
删除
NSArray *students = [Student MR_findByAttribute:@"name" withValue:@"Frank"];
for (Student *tmp in students) {
[tmp MR_deleteEntity];
}
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
注意:既然要删除首先得需要找到记录,根据条件匹配找到记录,然后删除,然后保存
心得体会
如果项目中对于操作数据库没有性能要求请使用 CoreData 相关的开源库吧.
CoreData 操作较为复杂, MagicalRecord 有着很多的特性,比如可以根据设置在主线程或者子线程中进行操作,方便快捷,能入榜最佳10大开源库自有其独到的地方,会使用 MagicalRecord 需要具备一定的 CoreData 相关知识,本人也只是现学现用,但深知其可以为开发带来的好处,使用数据库的朋友有着如下的一些选择.
1. SQLite3 C函数形式(本人之前做过干嵌入式开发,即使是这样也不推荐使用面向过程毫无对象概念的SQLite3,有更好的方式为什么不用呢?)
2. FMDB 对SQLite3的封装,有着对象的概念,熟悉SQ语句的朋友可以使用,但还没有做到对象与记录实时对应
3. CoreData 他做到了对象与记录实时对应关系,使用其自身的搜索体系(不用SQ语言),但其基本的操作以及配置让人望而却步
4. MagicalRecord 对 CoreData 官方的用法进行了人性化的封装,用过 CoreData 基本操作再使用 MagicalRecord 会深有体会
5. ObjectiveRecord 也是对 CoreData 的人性化封装,使用更加傻瓜,但傻瓜的代价就是牺牲了一些更强大的功能,在Github上搜索关键字即可
附录:
1.默认的就是在后台存储的,不会阻塞主线程
我在 CoreData+MagicalRecord.h 文件中定义了宏 MR_SHORTHAND ,所以在方法中不需要 MR_ 前缀了
以下为代码(提供block来通知存储成功,异步操作)
以下为打印信息
----------------------------------------------------------------------------------------------------------------------------------------------------
2014-03-13 11:17:43.616 StudyMagicalRecord[26416:60b] +[NSManagedObjectContext(MagicalRecord) MR_contextWithStoreCoordinator:](0x2f4498) -> Created Context UNNAMED
2014-03-13 11:17:43.616 StudyMagicalRecord[26416:60b] +[NSManagedObjectContext(MagicalRecord) MR_setRootSavingContext:](0x2f4498) Set Root Saving Context: <NSManagedObjectContext: 0xe74d910>
2014-03-13 11:17:43.617 StudyMagicalRecord[26416:60b] +[NSManagedObjectContext(MagicalRecord) MR_newMainQueueContext](0x2f4498) Created Main Queue Context: <NSManagedObjectContext: 0xe74e040>
2014-03-13 11:17:43.617 StudyMagicalRecord[26416:60b] +[NSManagedObjectContext(MagicalRecord) MR_setDefaultContext:](0x2f4498) Set Default Context: <NSManagedObjectContext: 0xe74e040>
2014-03-13 11:17:43.618 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74e040) → Saving <NSManagedObjectContext (0xe74e040): *** DEFAULT ***> on *** MAIN THREAD ***
2014-03-13 11:17:43.618 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74e040) → Save Parents? 1
2014-03-13 11:17:43.619 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74e040) → Save Synchronously? 0
2014-03-13 11:17:43.619 StudyMagicalRecord[26416:60b] time
2014-03-13 11:17:43.622 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalRecord) MR_contextWillSave:](0xe74e040) Context DEFAULT is about to save. Obtaining permanent IDs for new 1 inserted objects
2014-03-13 11:17:43.623 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74d910) → Saving <NSManagedObjectContext (0xe74d910): *** BACKGROUND SAVING (ROOT) ***> on *** MAIN THREAD ***
2014-03-13 11:17:43.624 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74d910) → Save Parents? 1
2014-03-13 11:17:43.624 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74d910) → Save Synchronously? 0
2014-03-13 11:17:43.625 StudyMagicalRecord[26416:1303] -[NSManagedObjectContext(MagicalRecord) MR_contextWillSave:](0xe74d910) Context BACKGROUND SAVING (ROOT) is about to save. Obtaining permanent IDs for new 1 inserted objects
2014-03-13 11:17:43.626 StudyMagicalRecord[26416:1303] __70-[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:]_block_invoke25(0xe74d910) → Finished saving: <NSManagedObjectContext (0xe74d910): *** BACKGROUND SAVING (ROOT) ***> on *** BACKGROUND THREAD ***
2014-03-13 11:17:43.627 StudyMagicalRecord[26416:60b] YES
----------------------------------------------------------------------------------------------------------------------------------------------------
2.如何关闭 MagicalRecord 提供的打印信息?
修改 MagicalRecord.h 23 行处的值,把 0 改为 1 即可.
Magical Record是用来操作Core Data的一个第三方工具,在介绍Magical Record 之前必须要先了解一下Core Data的基本概念
Core Data基本介绍
Core Data Stack
核心数据堆栈是由一个或多个与单个persistent store coordinator
关联的managed object contexts
组成,而persistent store coordinator
是和一个或多个persistent stores
关联在一起。堆栈包含了CoreData的所有组件查询,创建,操作managed objects
.
简单来说包含了:
- 一个包含了记录的
persistent store
.类似于数据库 - 一个在本地数据和对象之间的
persistent object store
- 一个聚合了所有存储的
persistent store coordinator
- 一个描述实体的
managed object model
- 一个容器包含
managed objects
的managed object context
容器
可能有点绕,不过一看图世界就清晰了
如下图:
![](https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/Art/single_persistent_stack.jpg)
Managed Object
Managed Object是一个模型对象(模型-视图-控制器的意义上),它代表了一个持久存储的记录。管理对象是实例NSManagedObject或子类NSManagedObject。
管理对象有一个实体的描述对象,告诉它代表着什么实体的引用。以这种方式,NSManagedObject可以表示任何实体不需要每个实体的唯一的子类。如果要实现自定义行为,例如计算派生属性值,或者为了实现验证逻辑可以使用一个子类。
还是来看图:
![](https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/Art/mapping_moc_record.jpg)
Managed Object Model
![](https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/Art/single_persistent_stack.jpg)
Manage Context Object
Manage Context Object代表单个对象的空间,,在核心数据的应用程序。管理对象上下文的一个实例的NSManagedObjectContext。它的主要职责是管理管理对象的集合。这些管理对象代表一个或多个持久存储的一个内部一致的看法。上下文是在管理对象的生命周期核心作用。
上下文是在核心数据堆栈中的中心对象。这是你用它来创建和获取管理对象和管理撤消和恢复操作的对象。内的给定范围内,有至多一个被管理目标代表在永久存储器的任何给定的记录。
![](https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/Art/moc_record.jpg)
上下文被连接到一个父对象存储。这通常是一个持久存储协调,但可能是另一个管理对象上下文。当你获取对象,上下文要求其父对象存储返回那些符合提取请求的对象。您对管理对象的修改,直到您保存的背景下不被提交到父store。
在某些应用中,你可能想保持独立组来管理对象和编辑这些对象的; 或者你可能需要执行使用一个上下文,同时允许用户与另一个对象交互的后台操作
Persistent Store Coordinator
哎!翻译太累了。直接上图吧
![](https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/Art/advanced_persistent_stack.jpg)
这张图把这个的架构解释得非常清楚
Fetch Request
![](https://developer.apple.com/library/ios/documentation/DataManagement/Devpedia-CoreData/Art/fetch_flow_of_data.jpg)
开始使用Magical Record
导入MagicalRecord.h
在项目的预编译文件*.pch
中。这保证了可以全局访问所需要的头文件。
使用了CocoaPods
或者MagicalRecord.framework
,用如下方式导入:
// Objective-C
#import <MagicalRecord/MagicalRecord.h>
// Swift
import MagicalRecord
如果是把源文件直接放到项目中,则直接#import "MagicalRecord.h"
接下里,在app delegate
的某些地方,比如- applicationDidFinishLaunching: withOptions:
或者-awakeFromNib
,使用下面的某一个方法来配置MagicalRecord
.
+ (void)setupCoreDataStack;
+ (void)setupAutoMigratingCoreDataStack;
+ (void)setupCoreDataStackWithInMemoryStore;
+ (void)setupCoreDataStackWithStoreNamed:(NSString *)storeName;
+ (void)setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName;
+ (void)setupCoreDataStackWithStoreAtURL:(NSURL *)storeURL;
+ (void)setupCoreDataStackWithAutoMigratingSqliteStoreAtURL:(NSURL *)storeURL;
每次调用Core Data
的堆栈的实例,提供给了这些实例的getter,setter方法。这些实例被MagicalRecord
很好的管理,被识别为默认方式。
当通过DEBUG
模式标识使用SQLite
数据库,不创建新的model
版本来改变model
将会引起MagicalRecord
自动的删除老的数据库并且自动的创建一个新的。这样可以节约很多时间--不需要每次都卸载重装app来让data model
改变,确保你的app不是用的DEBUG模式:当删除app数据的时候不告诉用户真的是一种很糟糕的方式
在你的app退出之前,你应该调用类方法+cleanUp
[MagicalRecord cleanUp];
这将会清理MagicalRecord
,比如自定义的错误处理,让通过MagicalRecord
创建的Core Data
堆栈为nil
.
使用Managed Object Contexts
创建新的上下文
一些简单的类方法用来帮助快速的你创建新的上下文
-
- [NSManagedObjectContext MR_newMainQueueContext]:
-
- [NSManagedObjectContext MR_newPrivateQueueContext]:
-
- [NSManagedObjectContext MR_newContextWithStoreCoordinator:…]: 允许你具体化
persistent store coordinator
为新的上下文,有一个NSPrivateQueueConcurrencyType
- [NSManagedObjectContext MR_newContextWithStoreCoordinator:…]: 允许你具体化
默认上下文
当使用CoreData
,你将不断的和两个主要的对象打交道,NSManagedObject
和 NSManagedObjectContext.
MagicalRecord
提高了一个简单的类方法来获取默认的NSManagedObjectContext
,这个上下文贯穿了你的app始终,这个上下文的操作会在在主线程中进行,并且对于单线程的app比较适合。
通过如下方式访问到默认的上下文:
NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
这个上下文将在MagicalRecord
任何使用了上下文的方法中使用,但是没有提供一个具体的NSManagedObjectContext
参数。
如果你需要创建一个不再主线程中使员工的上下文,使用:
NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_newContext];
这种方式将会创建一个和default context
有相同的对象和persistent store
.安全在其他线程使用。它将会默认的将default context
作为它的父上下文。
如果你想默认让myNewContext
实例化所有的fetch request
.使用类方法的方式
[NSManagedObjectContext MR_setDefaultContext:myNewContext];
注意:高度建议
default context
使用类型为NSMainQueueConcurrencyType
的上下文来创建并设置在主线程。
在后台线程中执行
MagicalRecord
提供了方法来设置,协调上下文在后台线程中使用。后台保存操作受到了UIView
动画使用Block
的方式,但也存在了一些不同
-
在你对实体进行改变了的block,绝对不在主线程中执行
-
单个的
NSManagedObjectContext
提供了block使用。
举个例子,你有一个Person实体,并且需要设置firstName和lastName,下面的代码展示了你怎样通过MagicalRecord
来设置后台上下文进行使用。
Person *person = ...;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
Person *localPerson = [person MR_inContext:localContext];
localPerson.firstName = @"John";
localPerson.lastName = @"Appleseed";
}];
在这个方法,具体的block
提供了一个合适的上下文让你进行操作,不需要担心去设置上下文,以便它告诉default context
已经做了。并且应该更新,因为是在其他线程里面改变进行的。
当执行完了saveBlock
,你可以在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];
}];
completion block
在主线程中被调用,为了UI更新更安全。
创建实体
在默认的上下文中插入一个实体,如下:
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
实体关联,你可以查询所有的person
实体从persistent store
通过如下方式实现:
NSArray *people = [Person MR_findAll];
传入一个具体的参数返回一个排序后的数组:
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName"
ascending:YES];
传入多个具体的参数返回一个排序后的数组
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName"
ascending:YES];
传入多个不同参数值得到排序结果,如果你不提供任何一个参数的默认值,就会默认使用你在model中的设置。
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"];
高级查找
如果想去具体化你的搜索,你可以使用谓词
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", @[dept1, dept2]];
NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];
返回NSFetchRequest
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];
NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];
关于每一行的调用, NSFetchRequest
和 NSSortDescriptor
作为排序的标配。
自定有Requset
Predicate *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];
查询实体数量
可以执行所有实体类型输血量在persistent store
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;
聚合操作
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];
存储实体
什么时候应该保存
总的来说,当数据发生改变的时候应该保存到persistent store(s)
.一些应用选择在应用终止的时候才保存。然而,在大多数场景下是不需要的。事实上,只在应用终止的时候保存,会有数据丢失的风险。万一你的应用崩溃了怎么办?用户将丢失他对数据所做的改变。那样的话是一种相当糟糕的体验,可以简单的避免。
如果你觉得执行保存话费了大量的时间,有几件事情需要考虑:
- 1.在后台线程保存:
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.将任务分解为多个小任务保存:比如大量数据的导入应被分解为小块,没有确切的原则来决定一次导入多少数据,你需要测量你应用的性能比如通过苹果的Instruments和tune.
解决长期运行中的保存
在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;
}];
确保仔细的读过[
- beginBackgroundTaskWithExpirationHandler:](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/index.html#//apple_ref/occ/instm/UIApplication/beginBackgroundTaskWithExpirationHandler:),不合适或者不需要的延长应用的存活时间可能让你的应用被拒。
使用模式
- 保存数据
- 查找实体
- 导入数据
- 线程安全
日志
MagicalRecord建立了在和Core Data交互的时候的日志。当错误发生的时候,这些错误将会被捕获。并且将打印到控制台。
日志被配置为输出调试信息(MagicalRecordLoggingLevelDebug)
在deug编译的时候默认的,将会输出日志信息MagicalRecordLoggingLevelError
在realease下。
日志通过[MagicalRecord setLoggingLevel:]
配置,使用下面的几种预定义的日主等级。
- MagicalRecordLogLevelOff:不开始日志
- MagicalRecordLoggingLevelError:记录所有错误
- MagicalRecordLoggingLevelWarn:记录警告和错误
- MagicalRecordLoggingLevelInfo:记录日志有用的信息,错误,警告
- MagicalRecordLoggingLevelDebug:所有调试信息
- MagicalRecordLoggingLevelVerbose:日志冗长的诊断信息,有用的信息,错误,警告
日志默认等级是 MagicalRecordLoggingLevelWarn
关闭日志
大多数人而言,这个不需要,设置日志等级为MagicalRecordLogLevelOff将会保证不再打印日志信息
甚至当使用了MagicalRecordLogLevelOff,快速检测检查可能被调用无论何时日志被调用。如果想绝对的关闭日志。你可以定义如下,当编译MagicalRecord的时候#define MR_LOGGING_DISABLED 1
请注意:这个之后再增加源码到项目中才会起作用。你也可以增加MagicalRecord项目的OTHER_CFLAGS为-DMR_LOGGING_DISABLED=1
日志在2.3.0版本有问题,不能正常的显示到控制器
google到了解决的方法副在下面
For the development branch (version 2.3.0 and higher) of Magical Record logging seems to still not work correctly. When imported like this: pod 'MagicalRecord', :git => 'https://github.com/magicalpanda/MagicalRecord', :branch => 'develop'
I have no logging output on my Xcode console. But I altered the post_install script of the Cocoapod. The following should enable logging: https://gist.github.com/Blackjacx/e5f3d62d611ce435775e
With that buildsetting included in GCC_PREPROCESSOR_DEFINITIONS logging of Magical Record can be controlled in 2.3.0++ by using [MagicalRecord setLoggingLevel:]
- 脚本:
post_install do |installer| installer.project.targets.each do |target| target.build_configurations.each do |config| # Enable the loggin for MagicalRecord # https://github.com/magicalpanda/MagicalRecord/wiki/Logging if target.name.include? "MagicalRecord" preprocessorMacros = config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] if preprocessorMacros.nil? preprocessorMacros = ["COCOAPODS=1"]; end preprocessorMacros << "MR_LOGGING_ENABLED=1" config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = preprocessorMacros end end end end
自己尝试遇到的坑
日记记录
2.3.0版本同样遇到了日志不能正常输出到控制台的问题,虽然能够拿到解决问题的脚步,但是自己在taget,buildsetting里面都设置了还是没有用。自己对cocopods管理的原理还不是很明白。
上下文的坑
NSManagedObjectContext
这个类是CoreData里面非常重要的类。它有父上下文和子上下文的概念。经过了漫长的爬坑,终于在苹果官方文档中找到了关于它详细的介绍。
这里只截取parent store
这节来讲
Managed object contexts
有一个父存储,通过它来检索数据,提交改变最开始在iOS5的之前,父存储一直是
persistent store coordinator
。在iOS5之后。父存储的类型可以是其他的Managed object contexts
。但是最终的根context必须是`persistent store coordinator``。协调者提高被管理的对象模型,调用各种对数据库的请求。如果父存储是一个
Managed object contexts
。查询,保存的操作是被父存储来协调的而不是persistent store coordinator
。这种方式有两个好处,
- 1.在其他线程中执行操作
2.管理废弃的编辑,比如监视窗口、view
第一种场景,父上下文能够通过不同的线程从子中获得请求,重点部分:当在上下文中保存所做的改变的时候,改变只会被提交一次存储,如果有子的上下文,改变将会推到他的父上下文,改变不会直接保存到数据库,直到根上下文被保存才会保存到数据库(根管理对象的上下文的父上下文为空)。除此之外,父上下文在保存之前不会从子中拉取数据的改变。如果你想最后提交数据的改变,必须保存子上下文,这样就可以推到父上下文中。
测试代码
上下文的创建时通过线程来控制,也就是上下文和线程相关。[[NSThread currentThread] threadDictionary];
返回的字典就是处理数据方面的。
if ([NSThread isMainThread])
{
return [self MR_defaultContext];
}
else
{
int32_t targetCacheVersionForContext = contextsCacheVersion;
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
NSManagedObjectContext *threadContext = [threadDict objectForKey:kMagicalRecordManagedObjectContextKey];
NSNumber *currentCacheVersionForContext = [threadDict objectForKey:kMagicalRecordManagedObjectContextCacheVersionKey];
// 保证两者同时存在,或者同时不存在
NSAssert((threadContext && currentCacheVersionForContext) || (!threadContext && !currentCacheVersionForContext),
@"The Magical Record keys should either both be present or neither be present, otherwise we're in an inconsistent state!");
// 不存在上下文
if ((threadContext == nil) || (currentCacheVersionForContext == nil) || ((int32_t)[currentCacheVersionForContext integerValue] != targetCacheVersionForContext))
{
// 创建新的上下文
threadContext = [self MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
[threadDict setObject:threadContext forKey:kMagicalRecordManagedObjectContextKey];
[threadDict setObject:[NSNumber numberWithInteger:targetCacheVersionForContext]
forKey:kMagicalRecordManagedObjectContextCacheVersionKey];
}
return threadContext;
}
在配置的时候就会默认创建两种上下文,一个根上下文,和协调者直接通信的,一个是主线程相关的默认上下文。默认上下文是根上下文的子。
- 有必要说一说
MR_saveWithBlock
这个方法,自己在写的时候就犯错了。
开看看实现
- (void)MR_saveWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;
{
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:self];
[localContext performBlock:^{
[localContext MR_setWorkingName:NSStringFromSelector(_cmd)];
if (block) {
block(localContext);
}
[localContext MR_saveWithOptions:MRSaveParentContexts completion:completion];
}];
}
是在当前的上下文中新建子然后通过子去保存,注意这里的保存方法有个参数MRSaveParentContexts
,会连同父上下文一起通常,
在保存的方法中有一段:
// 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];
类似于递归调用,最终会调用根上下文,也就是保存到了数据库。
但是在这之前有个逻辑想到重要。也就是保存的上下文该没有改变。如果被确定是没有改变的,那就不会中保存的逻辑。
__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;
}
最后来一段有问题的代码。
// 在默认的上下文中创建实体
Person *person = [Person MR_createEntity];
// 改变person,引起上下文的改变
person.name = @"test";
person.age = @(100);
[[NSManagedObjectContext MR_defaultContext] MR_saveWithBlock:^(NSManagedObjectContext * _Nonnull localContext) {
} completion:^(BOOL contextDidSave, NSError * _Nullable error) {
}];
这段代码不会保存成功。
因为在MR_saveWithBlock
创建一个继承自上下文的心的localContext。然而person所做的改变是在默认上下文中,也即是localContext的父上下文。判断是否改变是根据localContext来判断的,结果就是hasChanges为NO。最终导致保存不成功。
那么改变一下就可以了。也即是我们自己来控制保存。
如下:
// 在默认的上下文中创建实体
Person *person = [Person MR_createEntity];
// 改变person,引起上下文的改变
person.name = @"test";
person.age = @(100);
[[NSManagedObjectContext MR_defaultContext] MR_saveWithOptions:MRSaveParentContexts
completion:^(BOOL contextDidSave, NSError * _Nullable error) {
}];
总结:
多看官方文档,多看三方库wiki,多总结。
养成有耐心的习惯。勿急躁。