iOS-進(jìn)階整理03 - CoreData,實(shí)體關(guān)系,數(shù)據(jù)遷移

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

一、CoreData概述

Core Data是一個(gè)功能強(qiáng)大的層,位于SQLite數(shù)據(jù)庫(kù)之上,它避免了SQL的復(fù)雜性,能讓我們以更自然的方式與數(shù)據(jù)庫(kù)進(jìn)行交互。Core Data將數(shù)據(jù)庫(kù)行轉(zhuǎn)換為OC對(duì)象(托管對(duì)象)來(lái)實(shí)現(xiàn),這樣無(wú)需任何SQL知識(shí)就能操作他們。
但是sql的簡(jiǎn)單使用也不難啊,還是fmdb好用點(diǎn),可控性更強(qiáng)。

CoreData與sqlite的比較
sqlite
1.基于C接口,需要使用SQL語(yǔ)句,代碼繁瑣
2.在處理大量數(shù)據(jù)時(shí),表關(guān)系更直觀
3.在OC中不是可視化
CoreData
1.可視化,有undo/redo能力
2.可以實(shí)現(xiàn)多種文件格式NSSQLiteStoreType、NSBinaryStoreType、NSInMemoryStoreType、NSXMLStoreType等
3.蘋(píng)果官方API支持,與iOS結(jié)合更緊密

下面說(shuō)一下CoreData的核心類(lèi)和結(jié)構(gòu)

名稱(chēng)
作用

NSManagedObjectModel
對(duì)象模型,指定所用對(duì)象文件
NSPersistentStoreCoordinator
持久化存儲(chǔ)協(xié)調(diào)器,設(shè)置對(duì)象的存儲(chǔ)方式和數(shù)據(jù)存放位置
NSManagedObjectContext
對(duì)象管理上下文,負(fù)責(zé)數(shù)據(jù)的實(shí)際操作(重要)
NSEntityDescriptor
實(shí)體描述符,描述一個(gè)實(shí)體,可以用來(lái)生成實(shí)體對(duì)應(yīng)的對(duì)象
NSManagedObject
對(duì)象
NSFetchRequest
對(duì)象查詢(xún),相當(dāng)于SQL的Select語(yǔ)句

二、CoreData的使用

下面開(kāi)始建使用CoreData的工程

1.建立使用CoreData的工程

勾選Use Core Data選項(xiàng)
把Include Unit Tests和Include UI Tests選項(xiàng)取消勾選

屏幕快照 2016-05-20 下午3.32.36.png
2.進(jìn)入工程新建數(shù)據(jù)庫(kù)表

可以看到Test_CoreData___.xcdatamodeld文件就是我們生產(chǎn)的CoreData數(shù)據(jù)庫(kù)
點(diǎn)擊左下方的Add Entity可以添加新實(shí)體(表)
使用Attributes選項(xiàng)下的+ -號(hào)可以給表添加和刪除字段
在Relationships下面可以添加表之間的關(guān)系


3.生成實(shí)體類(lèi)

command+N新建類(lèi),選擇Core Data 下的NSManagedObject subclass文件新建,一直點(diǎn)next,選擇要新建的model,選擇新建類(lèi)的實(shí)體。
Use scalar properties for primitive data types選項(xiàng),如果勾選,數(shù)據(jù)庫(kù)中的integer類(lèi)型就在類(lèi)中變成int,不勾選,就變成NSNumber


產(chǎn)生了八個(gè)類(lèi),至此基礎(chǔ)工作才完成,進(jìn)入AppDelegate里面可以看到一些新的關(guān)于CoreData的方法


我們現(xiàn)在實(shí)現(xiàn)以學(xué)生信息的表視圖,可以添加修改刪除和查詢(xún)。

1.我們先寫(xiě)兩個(gè)方法,分別用來(lái)得到Appdelegate對(duì)象和context對(duì)象

//為了得到臨時(shí)數(shù)據(jù)庫(kù)context  
//先得到Appdelegate對(duì)象  
-(AppDelegate*)appDelegate  
{  
    //整個(gè)應(yīng)用程序的代理  
    return [UIApplication sharedApplication].delegate;  
}  
  
//得到context對(duì)象  
-(NSManagedObjectContext*)context  
{  
    return [[self appDelegate]managedObjectContext];//這是在Appdelegate里面系統(tǒng)自動(dòng)添加的發(fā)  
}  
2.再寫(xiě)增刪改查的四個(gè)方法
//為數(shù)據(jù)庫(kù)增加數(shù)據(jù)  
-(void)insertData  
{  
    //通過(guò)實(shí)體描述對(duì)象,獲得實(shí)體(相當(dāng)于得到我們要操作的數(shù)據(jù)庫(kù)表)  
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Student" inManagedObjectContext:[self context]];  
    //寫(xiě)入數(shù)據(jù)  
    for (int i = 0; i< 10; i++) {  
        //插入操作  
        Student *student = [[Student alloc]initWithEntity:entity insertIntoManagedObjectContext:[self context]];  
        student.name = [NSString stringWithFormat:@"若風(fēng)%d",i];  
        student.age = 20+i;  
        student.gender = @"m";  
    }  
    //將context中存儲(chǔ)的數(shù)據(jù)同步到真實(shí)的文件中  
    [[self appDelegate]saveContext];//這個(gè)方法是在AppDelegate中寫(xiě)好的  
}  
  
//查詢(xún)操作  
-(NSArray*)fetch  
{  
    //構(gòu)造出需要查詢(xún)的實(shí)體  
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Student" inManagedObjectContext:[self context]];  
    //初始化查詢(xún)工具  
    NSFetchRequest *req = [[NSFetchRequest alloc]init];  
    //為查詢(xún)工具設(shè)置所需要查詢(xún)的實(shí)體  
    [req setEntity:entity];  
    //設(shè)置查詢(xún)條件,不加就是查詢(xún)?nèi)? 
    //NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 24"];  
    //[req setPredicate:predicate];  
      
    //排序方法  
    NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES];  
    [req setSortDescriptors:@[sort]];  
      
    //執(zhí)行查詢(xún)(相當(dāng)于執(zhí)行查詢(xún)語(yǔ)句)  
    NSArray *allDataArray = [[self context]executeFetchRequest:req error:nil];  
    if (allDataArray&&allDataArray.count) {  
        return allDataArray;  
    }  
    else  
    {  
        NSLog(@"no result");  
        return nil;  
    }  
}  
  
//刪除數(shù)據(jù)  
-(void)deleteData  
{  
    //從當(dāng)前數(shù)據(jù)取出要?jiǎng)h除的對(duì)象  
    Student *delStu = [self.allDataArray lastObject];  
    //刪除context里的  
    [[self context]deleteObject:delStu];  
      
    //同步  
    [[self appDelegate]saveContext];  
}  
  
  
//更新數(shù)據(jù)  
-(void)updateData  
{  
    //得到要查詢(xún)的表  
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Student" inManagedObjectContext:[self context]];  
    //建立查詢(xún)的工具類(lèi)  
    NSFetchRequest *req = [[NSFetchRequest alloc]init];  
    [req setEntity:entity];  
    //謂詞  
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS %@",@"若風(fēng)"];  
    [req setPredicate:predicate];  
      
    //結(jié)果  
    NSArray *array = [[self context]executeFetchRequest:req error:nil];  
    //遍歷結(jié)果集更改對(duì)象屬性  
    for (Student *stu in array) {  
        stu.name = @"傾城";  
    }  
    //更新操作需要同步  
    [[self appDelegate]saveContext];  
}  

其他的tableView的設(shè)置

#import "RootViewController.h"  
#import "AppDelegate.h"http://導(dǎo)入目的為,得到臨時(shí)數(shù)據(jù)庫(kù)(為了應(yīng)用可以和數(shù)據(jù)文件交互)(context)  
#import "Student+CoreDataProperties.h"  
  
@interface RootViewController ()<UITableViewDelegate,UITableViewDataSource>  
  
@property (nonatomic,retain)UITableView *myTabelView;  
@property (nonatomic,retain)NSMutableArray *allDataArray;//表視圖用的數(shù)組  
    
@end  
  
//添加按鈕  
-(void)addData:(UIBarButtonItem*)sender  
{  
    [self insertData];  
}  
  
//查詢(xún)按鈕  
-(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:@"查詢(xún)" 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];  
      
    //設(shè)置代理  
    _myTabelView.delegate = self;  
    _myTabelView.dataSource = self;  
      
    //注冊(cè)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;  
}  

實(shí)現(xiàn)效果,上面的按鈕分別是 增 改 刪 查


三、CoreData實(shí)體之間的關(guān)系,數(shù)據(jù)庫(kù)遷移

1.實(shí)體之間的關(guān)系,類(lèi)似于表之間的外鍵關(guān)系。但是兩個(gè)實(shí)體比不需要一個(gè)相同的字段。

舉個(gè)栗子,一個(gè)Students實(shí)體,一個(gè)MyClass實(shí)體,給他們添加一個(gè)關(guān)系,MyClass設(shè)置為一對(duì)多,Students設(shè)置為一對(duì)一。
這樣,每次在添加了一個(gè)Student數(shù)據(jù)時(shí),都可以把他添加到一條MyClass數(shù)據(jù)里。
我們拿出一條MyClass數(shù)據(jù),也可以從中取出該MyClass里面的所有Student

(1)首先在MyClass的Relationships中添加一條關(guān)系,Relationship項(xiàng)為關(guān)系的名稱(chēng),Destination為鏈接的數(shù)據(jù)實(shí)體(表),type選擇To Many,表示一個(gè)班級(jí)可以有多個(gè)學(xué)生
Delete Rule表示刪除規(guī)則,規(guī)定如果刪除一個(gè)班級(jí),班級(jí)里的學(xué)生如何處理。

nullify斬?cái)鄬W(xué)生和班級(jí)的關(guān)系
NoAction 指向空,一般不選
cascade刪除班級(jí)里的學(xué)生
deny 要把班級(jí)里的學(xué)生刪除,才能刪除這個(gè)班級(jí)


(2)然后在Student實(shí)體的RelationShips中添加一個(gè)關(guān)系,type設(shè)置為T(mén)o One,一個(gè)學(xué)生只能對(duì)應(yīng)一個(gè)班級(jí)


(3)這樣關(guān)系就建立好了,我們?cè)偻ㄟ^(guò)上面的方法建立實(shí)體的類(lèi)
會(huì)發(fā)現(xiàn)在MyClass+CoreDataProperties.h中多了幾個(gè)方法和一個(gè)屬性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;  
  
//這個(gè)屬性就是該條MyClass記錄里面所有的Student信息  
 @property (nullable, nonatomic, retain) NSSet<Student *> *student;  
  
@end  
  
@interface MyClass (CoreDataGeneratedAccessors)  
  
//這四個(gè)方法就是給MyClass添加學(xué)生和刪除學(xué)生的方法。  
//當(dāng)學(xué)生記錄建立好后,使用這些方法,就可以把學(xué)生添加到對(duì)應(yīng)的MyClass記錄里  
  
- (void)addStudentObject:(Student *)value;  
- (void)removeStudentObject:(Student *)value;  
- (void)addStudent:(NSSet<Student *> *)values;  
- (void)removeStudent:(NSSet<Student *> *)values;  
  
@end  

同時(shí),在Student+CoreDataProperties.h中也多了一個(gè)屬性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;  
  
//表示該學(xué)生所屬的MyClass  
 @property (nullable, nonatomic, retain) MyClass *myclass;  
  
@end  

使用的時(shí)候這樣用

  //創(chuàng)建一個(gè)MyClass對(duì)象  
  MyClass *myClass = [NSEntityDescription insertNewObjectForEntityForName:@"LanOuClass" inManagedObjectContext:[self context]];  
   
  //創(chuàng)建一個(gè)Student對(duì)象  
  Student *stu = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:[self context]];  
  
  //將學(xué)生和班級(jí)建立關(guān)聯(lián)  
  [myClass addStudentObject:stu];  
  
  //移除關(guān)聯(lián)  
  [myClass removeStudentObject:stu];  
  
  //得到myClass下的所有student  
   for (Student *stu in myClass.student) {  
        //對(duì)stu進(jìn)行操作  
    }  
2.數(shù)據(jù)庫(kù)的遷移

我們知道,當(dāng)一個(gè)項(xiàng)目做得差不多了,突然需要修改一下數(shù)據(jù)表,比如添加字段,修改字段類(lèi)型,改變主外鍵關(guān)系時(shí),會(huì)出無(wú)限問(wèn)題。
如果只是加一個(gè)表,就不用數(shù)據(jù)遷移
在CoreData的實(shí)體需要修改時(shí),可以通過(guò)數(shù)據(jù)庫(kù)遷移的方式
數(shù)據(jù)遷移有三個(gè)階段
(1)創(chuàng)建基于源實(shí)例對(duì)象的目標(biāo)實(shí)例對(duì)象
(2)重新建立聯(lián)系
(3)驗(yàn)證與保存

a. 按下圖的Creat NSManagedObject Subclass可以新建一個(gè)實(shí)例對(duì)象。然后可以按我們的需求修改里面的表
b. 在下圖右下角Current選項(xiàng)里選擇新建的實(shí)例對(duì)象

c. 在Appdelegate.m的- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 方法里的這句代碼的option參數(shù)里面添加一個(gè)字典
@{NSMigratePersistentStoresAutomaticallyOption:@YES,NSInferMappingModelAutomaticallyOption:@YES}
表示支持版本遷移,以及版本遷移后自動(dòng)設(shè)置映射關(guān)系
就可以用了,好累..


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

d.如果運(yùn)行還報(bào)錯(cuò),把模擬器的app刪除了重新生成

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容