储存篇 - CoreData使用大全

【一】前言

Core Data框架提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite3数据库文件中,也能够将保存在数据库中的数据还原成OC对象。在此数据操作期间,不需要编写任何SQL语句。使用此功能,要添加CoreData.framework和导入主头文件 <CoreData/CoreData.h>。

【二】各种类作用的介绍

创建Core Data Stack

  • iOS10中利用NSPersistentContainer
  • iOS10之前涉及NSManagedObjectContext、NSPersistentStoreCoordinator、NSManagedObjectModel、NSPersistentStore这些类

【三】手动创建CoreData数据

我们创建一个和平常一样的工程,不需要勾选Use Core Data:

一、创建模型文件

1、进入创建新文件,command+N或者如下图

2、选择文件类型, 如下图:

3、设置文件名,如下图:

4、模型文件创建成功,会出现以后


建好后你会发现工程中多了 XXXXXXX.xcdatamodeld,我们需要在这里添加实体(首字母大写)和实体的属性。

二、创建实体

1、利用可视化的方式创建实体,实体的功能就类似于我们的Model类,具体操作如下如:

在传统的项目中我们都使用OC变成,但是CoreData默认使用的是Swift语言,所以我们要设置回来OC,详情见图片


同时需要将codegen选为Manaul/None


image.png

这里我们需要创建Person和Card的实体以及实体属性:

实体间的关系:选中Person实体,在Person中添加card属性:

image.png

选中Card实体,在Card中添加person属性:

添加完成后,他们关系如下:

三、创建实体类

利用可视化创建了实体,但是我们要想获取对应的数据和名称,就必须关联类,因此要创建实体类,创建步骤如下:

1、选中 .xcdatamodeld 文件通过 Editor 创建:NSManagedObject subclass类文件

2、生成了4个分类
分别为A+CoreDataClass.h, A+CoreDataClass.m, A+CoreDataProperties.h,A+CoreDataProperties.m
前2个为正式类文件(可以在需要用的地方直接引用这个类,这个类内部已经引用了后面两个类), 后两个为属性类文件。

四、手动创建CoreData的使用
值得注意的是:下面的例子中我们可以直接使用创建的目的实体类如:Dog,也可以使用NSManagedObject 这一公共实体类,可以使用KVC赋值,也可以使用 . 属性 的方式直接赋值。
    NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:_context];
    [person setValue:@"lifengfeng" forKey:@"name"];
    [person setValue:[NSNumber numberWithInt:23] forKey:@"age"];
   
   +++++++++++++++++   另一个方式  +++++++++++++++++
    Dog *dog = [NSEntityDescription insertNewObjectForEntityForName:@"Dog" inManagedObjectContext:self.myContext];
    dog.name = @"name1";
    dog.age = @"12";

    for (Dog *obj in objs) {
        NSLog(@"name=%@", obj.name);
    }

1、搭建上下文环境

//1、创建模型对象
    //获取模型路径
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"NewCodeDataModel" withExtension:@"momd"];//NewCodeDataModel.xcdatamodeld
    //根据模型文件创建模型对象
    NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    
    //2、创建持久化助理
    //利用模型对象创建助理对象
    NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    
    //数据库的名称和路径
    NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *sqlPath = [docStr stringByAppendingPathComponent:@"mySqlite.sqlite"];
    NSLog(@"path = %@", sqlPath);
    NSURL *sqlUrl = [NSURL fileURLWithPath:sqlPath];
    //设置数据库相关信息
    [store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqlUrl options:nil error:nil];
    
    //3、创建上下文
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    //关联持久化助理
    [context setPersistentStoreCoordinator:store];
    self.myContext = context;

   其中
   持久化存储库的类型(addPersistentStoreWithType:参数):
  (1)NSSQLiteStoreType  SQLite数据库
  (2)NSBinaryStoreType  二进制平面文件
  (3)NSInMemoryStoreType 内存库,无法永久保存数据

   ConcurrencyType可选项(initWithConcurrencyType:参数):
  (1)NSConfinementConcurrencyType 这个是默认项,每个线程一个独立的Context,主要是为了兼容之前的设计。
  (2)NSPrivateQueueConcurrencyType 创建一个private queue(使用GCD),这样就不会阻塞主线程。
  (3)NSMainQueueConcurrencyType 创建一个main queue,使用主线程,会阻塞。

2、增:增加数据

/**
 增加数据
 */
-(void)addData{
    
    //传入上下文,创建一个Person实体对象:
    NSManagedObject *person =
    [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:_context];
    
    //设置简单属性:
    [person setValue:@"lifengfeng" forKey:@"name"];
    [person setValue:[NSNumber numberWithInt:23] forKey:@"age"];
    
    //传入上下文,创建一个Card实体对象:
    NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:_context];
    [card setValue:@"1234567890" forKey:@"no"];
    
    //设置Person和Card之间的关联关系:
    [person setValue:card forKey:@"card"];
    
    //利用上下文对象,将数据同步到持久化存储库:
    NSError *error = nil;
    BOOL success = [_context save:&error];
    if (!success) {
        [NSException raise:@"访问数据库错误!" format:@"%@", [error localizedDescription]];
    }else{
        NSLog(@"访问数据库成功!");
    }
    
    // 如果是想做更新操作:只要在更改了实体对象的属性后调用[context save:&error],就能将更改的数据同步到数据库
}

3、删:删除数据

/**
删除数据
*/
-(void)deleteData{
   
   //建立请求,连接实体
   NSFetchRequest *request = [[NSFetchRequest alloc] init] ;
   NSEntityDescription *person = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:_context];
   request.entity = person;
   
   //设置条件过滤(搜索name属性中包含”lifengfeng“的那条记录,注意等号必须加,可以有空格,也可以是==)
   NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", @"lifengfeng"];
   request.predicate = predicate;
   
   //遍历所有实体,将每个实体的信息存放在数组中
   NSArray *arr = [_context executeFetchRequest:request error:nil];
   
   //删除并保存
   if(arr.count)
   {
       for (NSEntityDescription *p in arr)
       {
           [_context deleteObject:p];
           NSLog(@"删除%@成功!",p.name);
       }
       //保存
       [_context save:nil];
   }
}

4、改:修改数据

/**
 修改数据
 */
-(void)updateData{
    //建立请求,连接实体
    NSFetchRequest *request = [[NSFetchRequest alloc] init] ;
    NSEntityDescription *person = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:_context];
    request.entity = person;
    
    //设置条件过滤(搜索所有name属性不为“lifengfeng”的数据)
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name!=%@", @"lifengfeng"];
    request.predicate = predicate;
    
    //遍历所有实体,将每个实体的信息存放在数组中
    NSArray *arr = [_context executeFetchRequest:request error:nil];
    
    //更改并保存
    if(arr.count)
    {
        for (NSEntityDescription *p in arr)
        {
            p.name = @"更改";
        }
        //保存
        [_context save:nil];
    }
    else
    {
        NSLog(@"无检索");
    }
}

5、查:查询数据

/**
 查询数据
 */
-(void)queryData{
    
    //初始化一个查询请求:
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    
    //设置要查询的实体:
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:_context];
    request.entity = entity;
    
    //设置排序(按照age降序):
    NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
    request.sortDescriptors = [NSArray arrayWithObject:sort];
    
    //设置条件过滤(name like '%lifengfeng%'):
    //设置条件过滤时,数据库里面的%要用*来代替
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@", @"*lifengfeng*"];
    request.predicate = predicate;
    //执行请求:
    NSError *error = nil;
    NSArray *objs = [_context executeFetchRequest:request error:&error];
    if (error) {
        [NSException raise:@"查询错误" format:@"%@", [error localizedDescription]];
    }
    //遍历数据:
    for (NSManagedObject *obj in objs) {
        NSLog(@"name=%@", [obj valueForKey:@"name"]);
    }
}

【四】使用系统自动创建的CoreData

系统帮我们在AppDelegate中创建了一个NSPersistentContainer实例,以及一个saveContext方法。(并且已经帮我们创建了.xcdatamodeld模型文件)
注意看saveContext,我们通过NSPersistentContainer的属性viewContext拿到NSManagedObjectContext对象,再通过save:方法进行数据的保存。
因为系统并没有帮我们适配旧系统,所以如果App要在非iOS10的旧系统运行,还需要做类似上面 “搭建上下文环境”的工作,因为那里的代码在iOS10以下和以上的代码中都可以执行。
如果是Xcode8之前的版本自动创建的Core Data Stack,会不一样(跟情况2类似),如下图:

一个大坑:

这里有个坑,在Xcode8中,Codegen下拉选择框中增加了Class/Definition这一选项,而且是默认的预设值,这时候系统会自动帮我们这个实体创建了NSManagedObject子类,我们不需要再创建实体类,最坑的是,这些自动创建的类,在导航面板是看不见的!!!然后你很容易再重复手动创建NSManagedObject子类,这时候就会报类似「duplicate symbol _OBJC_METACLASS_Photography in:...」这类错误。
所以,如果你想自己手动创建NSManagedObject子类,就要把系统预设的Class/Definition改为Manual/None。

使用系统自动创建的CoreData时,非常的方便,我们只需要在 xxxxx.xcdatamodeld 中添加好实体即可,然后就可以直接使用了。

    #import "Man+CoreDataClass.h"
    #import "AppDelegate.h"

    AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSPersistentContainer *container = delegate.persistentContainer;
    Man *man = [NSEntityDescription insertNewObjectForEntityForName:@"Man" inManagedObjectContext:container.viewContext];
    man.name = @"小明";
    man.height = @"180";
    
    // ++++++++ 保存数据 ++++++++
    NSError *error = nil;
    BOOL success = [container.viewContext save:&error];
    if (!success) {
        [NSException raise:@"访问数据库错误!" format:@"%@", [error localizedDescription]];
    }else{
        NSLog(@"访问数据库成功!");
    }
    // ++++++++ 查询数据 ++++++++
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    //设置要查询的实体:
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Man" inManagedObjectContext:container.viewContext];
    request.entity = entity;
    NSError *error1 = nil;
    NSArray *objs = [container.viewContext executeFetchRequest:request error:&error];
    if (error1) {
        [NSException raise:@"查询错误" format:@"%@", [error1 localizedDescription]];
    }
    //遍历数据:
    for (NSManagedObject *obj in objs) {
        NSLog(@"name=%@", [obj valueForKey:@"name"]);
    }

【五】关于CoreData的版本迁移

应用场景:修改了实体的数据结构(比如说某个实体增加了一个特性),因为APP版本更新后沙盒中的NSDocumentDirectory 中的缓存数据都不会被清除,这时候就要进行版本迁移了,否则已经安装旧App的手机,在更新应用后,两边数据结构不一致导致不能识别,会崩溃。
步骤:

  • 选中.xcdatamodeld文件,Editor > Add Model Version,创建一个新版的.xcdatamodeld文件
  • 切换到新版的.xcdatamodeld文件(切换成功后会有绿色的勾),如下图:
  • 对.xcdatamodeld文件进行你想要的修改
  • 创建NSPersistentStore的时候,options参数传一个dictionary,值如下:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
    [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

 //在初始化的时候用到了版本迁移的设置
- (void)initializeCoreDataLessThaniOS10 {
    // Get managed object model(拿到模型文件,也就是.xcdatamodeld文件(我们会在初始化完Core data Stack后创建))
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MoveBand" withExtension:@"momd"];
    NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    NSAssert(mom != nil, @"Error initalizing Managed Object Model");
    
    // Create persistent store coordinator(创建NSPersistentStoreCoordinator对象(需要传入上述创建的NSManagedObjectModel对象))
    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
    
    // Creat managed object context(创建NSManagedObjectContext对象(_context是声明在.h文件的属性——因为其他类也要用到这个属性))
    _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    
    // assgin persistent store coordinator(赋值persistentStoreCoordinator)
    _context.persistentStoreCoordinator = psc;
    
    // Create .sqlite file(在沙盒中创建.sqlite文件)
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *documentsURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    NSURL *storeURL = [documentsURL URLByAppendingPathComponent:@"DataModel.sqlite"];
    
    // Create persistent store(异步创建NSPersistentStore并add到NSPersistentStoreCoordinator对象中,作用是设置保存的数据类型(NSSQLiteStoreType)、保存路径、是否支持版本迁移等)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        // 用于支持版本迁移的参数
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                                 [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
        NSError *error = nil;
        NSPersistentStoreCoordinator *psc = _context.persistentStoreCoordinator;
        
        // 备注,如果options参数传nil,表示不支持版本迁移
        NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType
                                                     configuration:nil
                                                               URL:storeURL
                                                           options:options
                                                             error:&error];
        NSAssert(store != nil, @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);
    });
}

最后值得注意的是:Core Data的延迟加载

Core Data不会根据实体中的关联关系立即获取相应的关联对象,比如通过Core Data取出Person实体时,并不会立即查询相关联的Card实体;当应用真的需要使用Card时,才会查询数据库,加载Card实体的信息。

【六】CoreData第三方库:MagicalRecord

地址:MagicalRecord

CoreData是苹果自家推出的一个持久化框架,使用起来更加面向对象。但是在使用过程中会出现大量代码,
而且CoreData学习曲线比较陡峭,如果掌握不好,在使用过程中很容易造成其他问题。

国外开发者开源了一个基于CoreData封装的第三方——MagicalRecord,就像是FMDB封装SQLite一样,
MagicalRecord封装的CoreData,使得原生的CoreData更加容易使用。并且MagicalRecord降低了CoreData的使用门槛,
不用去手动管理之前的PSC、MOC等对象。

添加MagicalRecord到项目中

MagicalRecord添加到项目中,和使用其他第三方一样,可以通过下载源码和CocoaPods两种方式添加。
但是不推荐直接拖源码到项目中,一是需要自己管理代码更新,另一个原因是,直接拖源码进项目是会报错的,修改起来很麻烦。

推荐通过CocoaPods安装MagicalRecord,需要在Podfile中加入下面命令,后续只需要通过命令来更新。

pod "MagicalRecord"

很多操作在这份MagicalRecord中文文档中都说明的很清楚,这里作简单归纳总结

1、AppDelegate中的设置

#import <MagicalRecord/MagicalRecord.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [MagicalRecord setupCoreDataStack];
    // ...
    return YES;
}
- (void)applicationWillTerminate:(UIApplication *)application
{
    [MagicalRecord cleanUp];
}

2、对象的储存和查询

  #import <MagicalRecord/MagicalRecord.h>

    // 获取上下文环境
    NSManagedObjectContext *localContext    = [NSManagedObjectContext MR_context];
    // 在当前上下文环境中创建一个新的 Person 对象.
    Man *person  = [Man MR_createEntityInContext:localContext];
    person.name = @"MagicalRecord存储";
    // 保存修改到当前上下文中.
    [localContext MR_saveToPersistentStoreAndWait];
    
    NSArray *peopleArray = [Man MR_findAll];
    for (Man *man in peopleArray) {
        NSLog(@"名称:%@",man.name);
    }

对于MagicalRecord 的使用感受就是,确实如它的名称一样,如此简洁和方便的实现了对象的增删改查,如此的充满魔力,关于的它的更多使用可以参考上面的中文文档,相信大家都可以熟练掌握这个好用的类库!

另外还有其他的第三方存储库: 可以存对象的数据库realm-cocoa使用时参考这篇文章:移动端数据库新王者:realm


参考文章:
iOS 开发之 CoreData
CoreData的使用
iOS CoreData数据库之创建详解
「死磕」Core Data——入门
认识CoreData - 初识CoreData

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

推荐阅读更多精彩内容