首页 > 技术文章 > Core Data & MagicalRecord

Jenaral 2016-07-13 10:50 原文


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.

实时映射记录是一种操作数据库的方式,一个数据库的表被封装成一个对象;这个对象中的一个实例会对应着该表中的一条记录.当创建一个对象时,一条记录也被插入到表中并保存起来.任何被加载的对象中的属性信息都从数据库中读取;当一个对象更新时,这个数据库表中对应的记录也会更新.这个被封装的类实现了实时操作的方法,且其属性一一对应于数据库中表的属性.

Wikipedia

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 即可.

 

 

推荐阅读