iOS CoreData(一)

CoreData简述

CoreData是ios系统推荐给我们的数据存储方案。在ios中数据存储有多种方式,包括NSUserDefault、writeToFile...、NSArchiver等,这里我主要说一下CoreData的用法。
  CoreData是数据库吗?很显然不是,它是一个完整的数据处理方案,封装的是底层的SQL语句,所以不需要我们会写SQL语句,就能够对数据进行处理,并提供了NSFetchResultsController可以将处理的结果显示在UITableView中。但是CoreData也是有缺点的,那就是它不如SQLite性能高。
下面详述CoreData API及其相关用法。

CoreData详述

一、CoreData框架

CoreData是ios3.0推出的数据存储方案。让我们先看一下CoreData API 主要架构,我们可以看到该API包含了40个类(Classes),3个协议(Protocols),以及3个参考(References)。下面我给大家汇总,一方面让大家对CoreData整体框架有一个宏观把握,另一方面也为深入研究做铺垫。
           Tab.1 Classes Lists

Classes Function
1.NSAsynchronousFetchRequest 继承自:NSPersistentStoreRequest
2.NSBatchDeleteRequest 继承自:NSPersistentStoreRequest
3.NSBatchUpdateRequest 继承自:NSPersistentStoreRequest
4.NSSaveChangesRequest 继承自:NSPersistentStoreRequest
5.NSFetchRequest 继承自:NSPersistentStoreRequest
6.NSBatchDeleteResult 继承自:NSPersistentStoreResult
7.NSBatchUpdateResult 继承自:NSPersistentStoreResult
8.NSAsynchronousFetchResult 继承自:NSPersistentStoreAsynchronousResult
9.NSPersistentStoreAsynchronousResult 继承自:NSPersistentStoreResult
10.NSPersistentStoreRequest 继承自:NSObject
11.NSPersistentStoreResult 继承自:NSObject
12.NSAtomicStore 继承自:NSPersistentStore
13.NSIncrementalStore 继承自:NSPersistentStore
14.NSPersistentStore 继承自:NSObject
15.NSAtomicStoreCacheNode 继承自:NSObject
16.NSConstraintConflict 继承自:NSObject
17.NSEntityDescription 继承自:NSObject
18.NSEntityMapping 继承自:NSObject
19.NSEntityMigrationPolicy 继承自:NSObject
20.NSFetchedResultsController 继承自:NSObject
21.NSIncrementalStoreNode 继承自:NSObject
22.NSManagedObject 继承自:NSObject
23.NSManagedObjectContext 继承自:NSObject
24.NSManagedObjectID 继承自:NSObject
25.NSManagedObjectModel 继承自:NSObject
26.NSMappingModel 继承自:NSObject
27.NSMergeConflict 继承自:NSObject
28.NSMergePolicy 继承自:NSObject
29.NSMigrationManager 继承自:NSObject
30.NSPersistentContainer 继承自:NSObject
31.NSPersistentStoreCoordinator 继承自:NSObject
32.NSPersistentStoreDescription 继承自:NSObject
33.NSExpressionDescription 继承自:NSPropertyDescription
34.NSFetchedPropertyDescription 继承自:NSPropertyDescription
35.NSRelationshipDescription 继承自:NSPropertyDescription
36.NSAttributeDescription 继承自:NSPropertyDescription
37.NSPropertyDescription 继承自:NSObject
38.NSPropertyMapping 继承自:NSObject
39.NSQueryGenerationToken 继承自:NSObject
40.NSFetchRequestExpression 继承自:NSExpression

很多的类吧,有人看晕了吗?好吧,那我现在列成树形结构,大家就清晰了。

Fig.1 CoreData类树形图

Tab.2 Protocols Lists

Protocols Function
1.NSFetchedResultsControllerDelegate 继承自:NSObject
2.NSFetchedResultsSectionInfo ---
3.NSFetchRequestResult 继承自:NSObject

Tab.3 Preference Lists

Protocols Function
1 Core Data Constants
2 Core Data Enumerations
3 Core Data Data Types

上面介绍了CoreData这个API的主要框架,具体可见CoreData官方

二、CoreData实现原理

原理

CoreData存储数据,有几个重要的类需要记住,这个我已经在Tab.1中用加粗进行显示。NSFetchRequest、NSEntityDescription、NSManagedObject、NSManagedObjectContext、NSManagedObjectModel。我们先看一下实现数据存储方案的原理。

Fig.2 CoreData实现数据存储原理

几个重要的类

1. NSManagedObjectModel 数据模型
  可以看做是数据库的模型结构,包含了各个实体的定义信息。有点像SQLite.sqlite文件,表示一个.xcdatamodeld文件。

//创建方式
// 获取模型文件路径
NSURL *modelURL = [[NSBundle mainBundle] URLForResource :modelName withExtension: @"person'];
//根据模型文件创建模型对象
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
//也可以利用下面方法,从应用改程序包中加载.xcdatamodeld模型文件
 NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];

2. NSPersistentStoreCoordinator 持久化存储协调器
  它是将对象的管理部分和持久化部分捆绑在一起,二者之间需要该调度器进行调节。它是最接近数据底层的。用来设置CoreData存储类型和存储路径。

//创建方式:在创建之前必须创建NSManagedObjectModel模型。
NSPersistentStoreCoordinator *persistent = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSError *error = nil;
NSString *sqlPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,  NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"person.sqlite"];
[persistent addPersistentStoreWithType:NSSQLiteStoreType configuration:nil  URL :[NSURL fileURLWithPath:sqlPath] options:nil  error:&error];
if (error) {
    if (fail) {
        fail(error);
    }
} else 
{
    self.context.persistentStoreCoordinator = self.persistent;
    if (success) {
        success();
    }
}


3. NSManagedObjectContext 管理上下文
  被管理数据的上下文,实际上是对所有数据库操作的一个缓存层,会把你所有的操作都先缓存起来避免大量磁盘IO造成不流畅,操作完成数据库后调用save进行持久化。可以理解成用来管理.xcdatamodeld中的数据。

//创建
self.context = [[NSManagedObjectContext alloc] init];
NSError *error = nil;
BOOL result = [self.context save: &error];
    if (!result) {
          if (fail) {
              fail(error);
          }
    } else {
          if (success)
              success();
    }



4. NSEntityDescription
用来描述实体,相当于数据库表中一组数据描述。

//创建方式:不能用alloc init方式创建,通过传入上下文和实体名称,创建一个名称对应的实体对象(相当于数据库中一组数据,包含各种字段)。
NSManagedObject  *newEntity = [NSEntityDescription insertNewObjectForEntityForName: entityName inManagedObjectContext:self.context];

三、CoreData实现数据存储

下面我们在代码层级上进行说明CoreData的使用。
1. 新建.xcdatamodeld文件。

新建.xcdatamodelId文件

新建完成如下所示。


新建完成后

2. 下面增加实体名称,可以点击person.xcdatamodeld下面的Add Entity。

增加实体文件

注意:实体名字可以修改,但是必须以大写字母开头。


修改实体名字

修改成功以后如下图所示。

实体名字修改完成

3. 增加实体里面的键值对,也就是增加数据库里面的字段。

增加字段

4. 创建关联类操控CoreData实体对象。

创建关联类

选择要管理的数据实体模型,这里默认给你已经勾选好了。

选择数据模型

选择要管理的实体,这里默认给你已经勾选好了。

选择管理实体

这里可以修改关联生成文件的类型是oc还是swift,我选择oc。

修改关联文件的类型

选择person.xcdatamodeld文件,再选择Editor-->Create NSManageObjectSubclass自动生成四个文件。

关联生成的四个文件

Command + B 编译一下。

编译结果

这个结果很让我诧异,知道是有的内容重复了但是不知道什么原因,后来在网上还是找到了解决办法。

解决办法

就是把红色框框内部的编译文件删除,避免重复编译。然后就好了。下面我们看看关联的NSManagedObject的子类里面都是什么。

PersonEntity+CoreDataClass.h文件中
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

NS_ASSUME_NONNULL_BEGIN

@interface PersonEntity : NSManagedObject

@end

NS_ASSUME_NONNULL_END

#import "PersonEntity+CoreDataProperties.h"
PersonEntity+CoreDataClass.m文件中

#import "PersonEntity+CoreDataClass.h"

@implementation PersonEntity

@end

PersonEntity+CoreDataProperties.h文件中

NS_ASSUME_NONNULL_BEGIN

@interface PersonEntity (CoreDataProperties)

+ (NSFetchRequest<PersonEntity *> *)fetchRequest;

@property (nonatomic) double weight;
@property (nullable, nonatomic, copy) NSString *name;
@property (nullable, nonatomic, copy) NSString *location;
@property (nonatomic) int16_t identify;
@property (nonatomic) BOOL gender;
@property (nonatomic) double height;
@property (nonatomic) int16_t age;

@end

NS_ASSUME_NONNULL_END

PersonEntity+CoreDataProperties.m

@implementation PersonEntity (CoreDataProperties)

+ (NSFetchRequest<PersonEntity *> *)fetchRequest {
    return [[NSFetchRequest alloc] initWithEntityName:@"PersonEntity"];
}

@dynamic weight;
@dynamic name;
@dynamic location;
@dynamic identify;
@dynamic gender;
@dynamic height;
@dynamic age;

@end

注意:这里有几点需要说明:

  • 这四各类都继承自NSManagedObject。
  • name和location可以是nullable,表示CoreData数据库存储的对象可能为nil,字段值可以为NULL。
  • PersonEntity+CoreDataProperties这个类不能利用alloc init方式进行创建。苹果API提供了几个专门创建实体对象的方法。

四、CoreData实现数据的增删改查

1. insert数据的增加

a) 根据Entity和Context获取一个新的NSManagedObject
    NSManagedObject *newEntity = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:self.context];
b) 通过setValue:forkey:给NSManagedObject对象赋值
   [newEntity setValue forKey: key];
c) 保存修改
   NSError *error = nil;
   BOOL result = [self.context  save: &error];

2. delete数据的删除

a) 通过查询获得需要删除的NSManagedObject, 一般用NSPredicate语句。
b) 在for循环中,调用deleteObject:方法逐个删除。
   [self.context deleteObject: entity];
c) self.context 进行保存。

3. update数据的修改

a) 通过查询找到需要修改的NSManagedObject 的集合,一般用NSPredicate。
b) 在for循环中,调用NSManagedObject的setValue:forkey: 方法给各个属性赋值。
c) 上下文进行保存。

4. read数据的查询

a) 创建请求对象。
  NSFetchRequest *request = [[NSFetchRequest alloc] init];
b) 设置需要查询的实体描述
  NSEntityDescription *desc = [NSEntityDescription entityForName:self.entityName  inManagedObjectContext: self.context];
c) 设置查询条件。
   NSPredicate *predicate = [NSPredicate predicateWithFormat:filterString];
d) 将实体和排序都赋值给请求对象。
  request.entity = desc;
  request.predicate = predicate;
  request.sortDescriptors = descArr;
e) 开始查询
  NSError *error = nil;
  // NSManagedObject对象集合
  NSArray *objs = [self.context executeFetchRequest:request error:&error];
  // 查询结果数目
  NSUInteger count = [self.context countForFetchRequest:request error:&error];

从上面可以看到,增删改查四种,查询是最复杂的,而且删除和修改也是基于查询的,可以这么认为查询如果会了,那么增删改查就差不多了。其他的就是保存等其他的操作,那就简单了。

五、CoreData封装

下面对CoreData进行封装。

DDCoreDataManager.h文件

#import <Foundation/Foundation.h>
#import "CoreData/CoreData.h"

#define kDDCoreDataManagerModelName        (@"person")

@interface DDCoreDataManager : NSObject

@property (readonly, nonatomic, strong) NSManagedObjectContext *manageObjectContext;
@property (readonly, nonatomic, strong) NSManagedObjectModel *manageObjectModel;
@property (readonly, nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;

+ (instancetype)shareCoreDataManager;

- (void)saveContext;

@end

DDCoreDataManager.m文件

#import "DDCoreDataManager.h"


@implementation DDCoreDataManager

@synthesize manageObjectContext = _manageObjectContext;
@synthesize manageObjectModel = _manageObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

static DDCoreDataManager *coreDataManager;

#pragma mark - 单例

+ (instancetype)shareCoreDataManager
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        coreDataManager = [[self alloc] init];
    });
    return coreDataManager;
}


#pragma mark - 被管理对象模型

- (NSManagedObjectModel *)manageObjectModel
{
    if (_manageObjectModel != nil) {
        return _manageObjectModel;
    }
    
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:kDDCoreDataManagerModelName withExtension:@"momd"];
    _manageObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _manageObjectModel;
}

#pragma mark - document目录

- (NSURL *)applicationDocumentDirectory
{
    return [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject;
}

#pragma mark - 实例化调度器

- (NSPersistentStoreCoordinator *)persistentCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    NSURL *coordinatorStoreURL = [[self applicationDocumentDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.sqlite",kDDCoreDataManagerModelName]];
    
    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self manageObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:coordinatorStoreURL options:nil error:&error]) {
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
        dict[NSLocalizedFailureReasonErrorKey] =  @"There was an error creating or loading the application's saved data.";
        dict[NSUnderlyingErrorKey] = error;
        error = [NSError errorWithDomain:@"your error domain" code: 666    userInfo:dict];
        abort();
    }
    
    return _persistentStoreCoordinator;
}


#pragma mark - 实例化上下文context

- (NSManagedObjectContext *)manageObjectContext
{
    if (_manageObjectContext != nil) {
        return _manageObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentCoordinator];
    if (_persistentStoreCoordinator != nil) {
        _manageObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        _manageObjectContext.persistentStoreCoordinator = coordinator;
    }
    return _manageObjectContext;
}

#pragma mark - 上下文进行保存

- (void)saveContext
{
    NSManagedObjectContext *context = [self manageObjectContext];
    if (context != nil) {
        NSError *saveError = nil;
        if ([context hasChanges] && ![context save:&saveError]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", saveError, [saveError userInfo]);
        abort();
        }
    }
    
}

@end

总结

前面对CoreData进行了仔细的说明和封装,有什么不对的地方还请大家留言和批评指正。

致谢

尽管CoreData出的较早,但是使用起来总感觉没那么顺,看了不少技术大牛的博客,多敲代码和时间长了才慢慢了解和熟悉。这里借鉴了很多技术大牛的经验,感谢!

相关资料和博客

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

推荐阅读更多精彩内容