iOS-进阶整理03 - CoreData,实体关系,数据迁移

demo在此 https://github.com/DaLiWangCC/MyOpen

一、CoreData概述

Core Data是一个功能强大的层,位于SQLite数据库之上,它避免了SQL的复杂性,能让我们以更自然的方式与数据库进行交互。Core Data将数据库行转换为OC对象(托管对象)来实现,这样无需任何SQL知识就能操作他们。
但是sql的简单使用也不难啊,还是fmdb好用点,可控性更强。

CoreData与sqlite的比较
sqlite
1.基于C接口,需要使用SQL语句,代码繁琐
2.在处理大量数据时,表关系更直观
3.在OC中不是可视化
CoreData
1.可视化,有undo/redo能力
2.可以实现多种文件格式NSSQLiteStoreType、NSBinaryStoreType、NSInMemoryStoreType、NSXMLStoreType等
3.苹果官方API支持,与iOS结合更紧密

下面说一下CoreData的核心类和结构

名称
作用

NSManagedObjectModel
对象模型,指定所用对象文件
NSPersistentStoreCoordinator
持久化存储协调器,设置对象的存储方式和数据存放位置
NSManagedObjectContext
对象管理上下文,负责数据的实际操作(重要)
NSEntityDescriptor
实体描述符,描述一个实体,可以用来生成实体对应的对象
NSManagedObject
对象
NSFetchRequest
对象查询,相当于SQL的Select语句

二、CoreData的使用

下面开始建使用CoreData的工程

1.建立使用CoreData的工程

勾选Use Core Data选项
把Include Unit Tests和Include UI Tests选项取消勾选

屏幕快照 2016-05-20 下午3.32.36.png
2.进入工程新建数据库表

可以看到Test_CoreData___.xcdatamodeld文件就是我们生产的CoreData数据库
点击左下方的Add Entity可以添加新实体(表)
使用Attributes选项下的+ -号可以给表添加和删除字段
在Relationships下面可以添加表之间的关系


3.生成实体类

command+N新建类,选择Core Data 下的NSManagedObject subclass文件新建,一直点next,选择要新建的model,选择新建类的实体。
Use scalar properties for primitive data types选项,如果勾选,数据库中的integer类型就在类中变成int,不勾选,就变成NSNumber


产生了八个类,至此基础工作才完成,进入AppDelegate里面可以看到一些新的关于CoreData的方法


我们现在实现以学生信息的表视图,可以添加修改删除和查询。

1.我们先写两个方法,分别用来得到Appdelegate对象和context对象

//为了得到临时数据库context  
//先得到Appdelegate对象  
-(AppDelegate*)appDelegate  
{  
    //整个应用程序的代理  
    return [UIApplication sharedApplication].delegate;  
}  
  
//得到context对象  
-(NSManagedObjectContext*)context  
{  
    return [[self appDelegate]managedObjectContext];//这是在Appdelegate里面系统自动添加的发  
}  
2.再写增删改查的四个方法
//为数据库增加数据  
-(void)insertData  
{  
    //通过实体描述对象,获得实体(相当于得到我们要操作的数据库表)  
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Student" inManagedObjectContext:[self context]];  
    //写入数据  
    for (int i = 0; i< 10; i++) {  
        //插入操作  
        Student *student = [[Student alloc]initWithEntity:entity insertIntoManagedObjectContext:[self context]];  
        student.name = [NSString stringWithFormat:@"若风%d",i];  
        student.age = 20+i;  
        student.gender = @"m";  
    }  
    //将context中存储的数据同步到真实的文件中  
    [[self appDelegate]saveContext];//这个方法是在AppDelegate中写好的  
}  
  
//查询操作  
-(NSArray*)fetch  
{  
    //构造出需要查询的实体  
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Student" inManagedObjectContext:[self context]];  
    //初始化查询工具  
    NSFetchRequest *req = [[NSFetchRequest alloc]init];  
    //为查询工具设置所需要查询的实体  
    [req setEntity:entity];  
    //设置查询条件,不加就是查询全部  
    //NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 24"];  
    //[req setPredicate:predicate];  
      
    //排序方法  
    NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES];  
    [req setSortDescriptors:@[sort]];  
      
    //执行查询(相当于执行查询语句)  
    NSArray *allDataArray = [[self context]executeFetchRequest:req error:nil];  
    if (allDataArray&&allDataArray.count) {  
        return allDataArray;  
    }  
    else  
    {  
        NSLog(@"no result");  
        return nil;  
    }  
}  
  
//删除数据  
-(void)deleteData  
{  
    //从当前数据取出要删除的对象  
    Student *delStu = [self.allDataArray lastObject];  
    //删除context里的  
    [[self context]deleteObject:delStu];  
      
    //同步  
    [[self appDelegate]saveContext];  
}  
  
  
//更新数据  
-(void)updateData  
{  
    //得到要查询的表  
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Student" inManagedObjectContext:[self context]];  
    //建立查询的工具类  
    NSFetchRequest *req = [[NSFetchRequest alloc]init];  
    [req setEntity:entity];  
    //谓词  
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS %@",@"若风"];  
    [req setPredicate:predicate];  
      
    //结果  
    NSArray *array = [[self context]executeFetchRequest:req error:nil];  
    //遍历结果集更改对象属性  
    for (Student *stu in array) {  
        stu.name = @"倾城";  
    }  
    //更新操作需要同步  
    [[self appDelegate]saveContext];  
}  

其他的tableView的设置

#import "RootViewController.h"  
#import "AppDelegate.h"//导入目的为,得到临时数据库(为了应用可以和数据文件交互)(context)  
#import "Student+CoreDataProperties.h"  
  
@interface RootViewController ()<UITableViewDelegate,UITableViewDataSource>  
  
@property (nonatomic,retain)UITableView *myTabelView;  
@property (nonatomic,retain)NSMutableArray *allDataArray;//表视图用的数组  
    
@end  
  
//添加按钮  
-(void)addData:(UIBarButtonItem*)sender  
{  
    [self insertData];  
}  
  
//查询按钮  
-(void)fetchData:(UIBarButtonItem*)sender  
{              
    self.dataArray = [self fetch];  
    self.allDataArray = [NSMutableArray arrayWithArray:[self fetch]];  
  
    //刷新UI  
    [_myTabelView reloadData];  
}  
    
- (void)viewDidLoad {  
    [super viewDidLoad];  
      
    self.navigationItem.title = @"CoreData";  
      
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addData:)];  
    UIBarButtonItem *serBarBtn = [[UIBarButtonItem alloc]initWithTitle:@"查询" style:UIBarButtonItemStylePlain target:self action:@selector(fetchData:)];  
    // Do any additional setup after loading the view.  
    UIBarButtonItem *delBarBtn = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteData)];  
      
    UIBarButtonItem *upDateBarBtn = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(updateData)];  
      
    self.navigationItem.rightBarButtonItems = @[serBarBtn,delBarBtn,upDateBarBtn];  
      
    //添加tableView  
    _myTabelView = [[UITableView alloc]initWithFrame:self.view.frame style:UITableViewStylePlain];  
    [self.view addSubview:_myTabelView];  
      
    //设置代理  
    _myTabelView.delegate = self;  
    _myTabelView.dataSource = self;  
      
    //注册cell  
    [_myTabelView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"CELL"];  
}  
  
#pragma mark -- cell  
  
//rows  
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section  
{    
    return _allDataArray.count;  
}  
      
//cell in  
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
{  
    UITableViewCell *cell = [_myTabelView dequeueReusableCellWithIdentifier:@"CELL" forIndexPath:indexPath];  
      
    Student *stu = _allDataArray[indexPath.row];  
      
    cell.textLabel.text = [NSString stringWithFormat:@"姓名:%@,年龄:%d",[stu name],stu.age];  
      
    cell.imageView.image = [UIImage imageNamed:@"bufu.jpg"];  
      
    return cell;  
}  

实现效果,上面的按钮分别是 增 改 删 查


三、CoreData实体之间的关系,数据库迁移

1.实体之间的关系,类似于表之间的外键关系。但是两个实体比不需要一个相同的字段。

举个栗子,一个Students实体,一个MyClass实体,给他们添加一个关系,MyClass设置为一对多,Students设置为一对一。
这样,每次在添加了一个Student数据时,都可以把他添加到一条MyClass数据里。
我们拿出一条MyClass数据,也可以从中取出该MyClass里面的所有Student

(1)首先在MyClass的Relationships中添加一条关系,Relationship项为关系的名称,Destination为链接的数据实体(表),type选择To Many,表示一个班级可以有多个学生
Delete Rule表示删除规则,规定如果删除一个班级,班级里的学生如何处理。

nullify斩断学生和班级的关系
NoAction 指向空,一般不选
cascade删除班级里的学生
deny 要把班级里的学生删除,才能删除这个班级


(2)然后在Student实体的RelationShips中添加一个关系,type设置为To One,一个学生只能对应一个班级


(3)这样关系就建立好了,我们再通过上面的方法建立实体的类
会发现在MyClass+CoreDataProperties.h中多了几个方法和一个属性student

#import "MyClass.h"  
  
NS_ASSUME_NONNULL_BEGIN  
  
@interface MyClass (CoreDataProperties)  
  
@property (nullable, nonatomic, retain) NSNumber *class_id;  
@property (nullable, nonatomic, retain) NSNumber *stu_count;  
  
//这个属性就是该条MyClass记录里面所有的Student信息  
 @property (nullable, nonatomic, retain) NSSet<Student *> *student;  
  
@end  
  
@interface MyClass (CoreDataGeneratedAccessors)  
  
//这四个方法就是给MyClass添加学生和删除学生的方法。  
//当学生记录建立好后,使用这些方法,就可以把学生添加到对应的MyClass记录里  
  
- (void)addStudentObject:(Student *)value;  
- (void)removeStudentObject:(Student *)value;  
- (void)addStudent:(NSSet<Student *> *)values;  
- (void)removeStudent:(NSSet<Student *> *)values;  
  
@end  

同时,在Student+CoreDataProperties.h中也多了一个属性myClass

#import "Student.h"  
  
NS_ASSUME_NONNULL_BEGIN  
  
@interface Student (CoreDataProperties)  
  
@property (nullable, nonatomic, retain) NSNumber *age;  
@property (nullable, nonatomic, retain) NSString *gender;  
@property (nullable, nonatomic, retain) NSString *name;  
  
//表示该学生所属的MyClass  
 @property (nullable, nonatomic, retain) MyClass *myclass;  
  
@end  

使用的时候这样用

  //创建一个MyClass对象  
  MyClass *myClass = [NSEntityDescription insertNewObjectForEntityForName:@"LanOuClass" inManagedObjectContext:[self context]];  
   
  //创建一个Student对象  
  Student *stu = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:[self context]];  
  
  //将学生和班级建立关联  
  [myClass addStudentObject:stu];  
  
  //移除关联  
  [myClass removeStudentObject:stu];  
  
  //得到myClass下的所有student  
   for (Student *stu in myClass.student) {  
        //对stu进行操作  
    }  
2.数据库的迁移

我们知道,当一个项目做得差不多了,突然需要修改一下数据表,比如添加字段,修改字段类型,改变主外键关系时,会出无限问题。
如果只是加一个表,就不用数据迁移
在CoreData的实体需要修改时,可以通过数据库迁移的方式
数据迁移有三个阶段
(1)创建基于源实例对象的目标实例对象
(2)重新建立联系
(3)验证与保存

a. 按下图的Creat NSManagedObject Subclass可以新建一个实例对象。然后可以按我们的需求修改里面的表
b. 在下图右下角Current选项里选择新建的实例对象

c. 在Appdelegate.m的- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 方法里的这句代码的option参数里面添加一个字典
@{NSMigratePersistentStoresAutomaticallyOption:@YES,NSInferMappingModelAutomaticallyOption:@YES}
表示支持版本迁移,以及版本迁移后自动设置映射关系
就可以用了,好累..


//版本迁移要在option加字典  
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL   
       options:@{NSMigratePersistentStoresAutomaticallyOption:@YES,NSInferMappingModelAutomaticallyOption:@YES}   
       error:&error])   

d.如果运行还报错,把模拟器的app删除了重新生成

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

推荐阅读更多精彩内容