iOS 本地数据持久化存储:
一、plist
二、归档
三、NSUserDefaults
四、NSFileManager
五、数据库
1、CoreData概述
CoreData是苹果自带的管理数据库的工具。使用Core Data有不少缘由,其中最简单的一条就是:它能让你为Model层写的代码的行数减小为原来的50%到70%。 这归功于以前提到的Core Data的特性。更妙的是,对于上述特性你也既不用去测试,也不用花功夫去优化。
Core Data拥有成熟的代码,这些代码经过单元测试来保证品质。经过了几个版本的发布,已经被高度优化。 它能利用Model层的信息和运行时的特性,而不经过程序层的代码实现。 除了提供强大的安全支持和错误处理外,它还提供了最优的内存扩展性,可实现有竞争力的解决方案。不使用Core Data的话,你须要花很长时间来起草本身的方案,解决各类问题,这样作效率不高。
另外:CoreData主要是iOS对SQLite数据库的封装。CoreData有 对象-关系 的映射的功能,能把OC的对象存储成数据库或 xml 等。若是数据存储使用的是coreData,那么读取时能够不使用 SQLite 语句。
2、关于Core Data常见的误解
一、 Core Data不是一个关系型数据库,也不是关系型数据库管理系统(RDBMS)。
Core Data 为数据变动管理、对象存储、对象读取恢复的功能提供了支持。 它可使用SQLite做为持久化存储的类型。 它自己并非一个数据库(这点很重要,好比,你可使用Core Data来记录数据变动,管理数据,但并不能用它向文件内存储数据)。
2 、它并不能取代你写代码的工做。虽然能够纯粹使用XCode的数据建模工具和Interface Builder来编写复杂程序,但在更多的程序中,你都本身动手写代码。
3、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),右键显示包内容,咱们看到是这样的。
4、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 中可视化地操做数据对象。
5、代码步骤
一、加载Core Data文件
二、将数据的存储方式设定为数据库
三、操做数据:增长
四、操做数据:查询
五、操做数据:修改
六、操做数据:删除css
MagicalRecord https://github.com/magicalpanda/MagicalRecordhtml
注意: MagicalRecord 在 ARC 下运做,Core Data 是 ORM 方案,听说带来的麻烦比好处多,且 Core Data 创建的表没有主键,但对于对数据库没有性能要求,进行简单的数据操做彻底够用,能简化无数的代码量.ios
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.git
在软件工程中,对象与数据库中的记录实时映射是一种设计模式,在处理关系型数据库的的软件中多有出现.这种设计模式被记录在 Martin Fowler 的<Patterns of Enterprise Application Architecture> 中.被操做对象的接口应该包含增删改查的方法,基本属性很少很多的恰好与数据库中的一条记录相对应.github
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.sql
实时映射记录是一种操做数据库的方式,一个数据库的表被封装成一个对象;这个对象中的一个实例会对应着该表中的一条记录.当建立一个对象时,一条记录也被插入到表中并保存起来.任何被加载的对象中的属性信息都从数据库中读取;当一个对象更新时,这个数据库表中对应的记录也会更新.这个被封装的类实现了实时操做的方法,且其属性一一对应于数据库中表的属性.数据库
- Wikipedia设计模式
MagicalRecord was inspired by the ease of Ruby on Rails' Active Record fetching. The goals of this code are:数组
MagicalRecord 灵感来自于简洁的Ruby语言中 Rails' Active Record 查询方式. MagicalRecord 这个开源库的核心思想是:安全
拙劣的翻译请勿见怪,如下是我在最新的 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的基本概念
核心数据堆栈是由一个或多个与单个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
容器可能有点绕,不过一看图世界就清晰了
以下图:
Managed Object是一个模型对象(模型-视图-控制器的意义上),它表明了一个持久存储的记录。管理对象是实例NSManagedObject或子类NSManagedObject。
管理对象有一个实体的描述对象,告诉它表明着什么实体的引用。以这种方式,NSManagedObject能够表示任何实体不须要每一个实体的惟一的子类。若是要实现自定义行为,例如计算派生属性值,或者为了实现验证逻辑可使用一个子类。
仍是来看图:
Manage Context Object表明单个对象的空间,,在核心数据的应用程序。管理对象上下文的一个实例的NSManagedObjectContext。它的主要职责是管理管理对象的集合。这些管理对象表明一个或多个持久存储的一个内部一致的见解。上下文是在管理对象的生命周期核心做用。
上下文是在核心数据堆栈中的中心对象。这是你用它来建立和获取管理对象和管理撤消和恢复操做的对象。内的给定范围内,有至多一个被管理目标表明在永久存储器的任何给定的记录。
上下文被链接到一个父对象存储。这一般是一个持久存储协调,但多是另外一个管理对象上下文。当你获取对象,上下文要求其父对象存储返回那些符合提取请求的对象。您对管理对象的修改,直到您保存的背景下不被提交到父store。
在某些应用中,你可能想保持独立组来管理对象和编辑这些对象的; 或者你可能须要执行使用一个上下文,同时容许用户与另外一个对象交互的后台操做
哎!翻译太累了。直接上图吧
这张图把这个的架构解释得很是清楚
导入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
.
一些简单的类方法用来帮助快速的你建立新的上下文
persistent store coordinator
为新的上下文,有一个NSPrivateQueueConcurrencyType当使用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];
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments]; NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];
关于每一行的调用, NSFetchRequest
和 NSSortDescriptor
做为排序的标配。
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)
.一些应用选择在应用终止的时候才保存。然而,在大多数场景下是不须要的。事实上,只在应用终止的时候保存,会有数据丢失的风险。万一你的应用崩溃了怎么办?用户将丢失他对数据所作的改变。那样的话是一种至关糟糕的体验,能够简单的避免。
若是你以为执行保存话费了大量的时间,有几件事情须要考虑:
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; }];
当应用在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; }];
确保仔细的读过[
MagicalRecord创建了在和Core Data交互的时候的日志。当错误发生的时候,这些错误将会被捕获。而且将打印到控制台。
日志被配置为输出调试信息(MagicalRecordLoggingLevelDebug)
在deug编译的时候默认的,将会输出日志信息MagicalRecordLoggingLevelError
在realease下。
日志经过[MagicalRecord setLoggingLevel:]
配置,使用下面的几种预约义的日主等级。
日志默认等级是 MagicalRecordLoggingLevelWarn
大多数人而言,这个不须要,设置日志等级为MagicalRecordLogLevelOff将会保证再也不打印日志信息
甚至当使用了MagicalRecordLogLevelOff,快速检测检查可能被调用不管什么时候日志被调用。若是想绝对的关闭日志。你能够定义以下,当编译MagicalRecord的时候#define MR_LOGGING_DISABLED 1
请注意:这个以后再增长源码到项目中才会起做用。你也能够增长MagicalRecord项目的OTHER_CFLAGS为-DMR_LOGGING_DISABLED=1
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,多总结。
养成有耐心的习惯。勿急躁。