目錄
一. CoreData簡介
- CoreData是apple自己封裝的數(shù)據(jù)庫操作的一個框架, 是一種面向?qū)ο蟮姆绞絹聿僮鲾?shù)據(jù)的框架, 底層是基于SQLite的
- 需要用到的幾個基本類型
二. CoreData使用
- 圖形化操作
- 常規(guī)的創(chuàng)建表格, 創(chuàng)建Cell
- 核心內(nèi)容:創(chuàng)建上下文對象
- DBManager類中 "增刪改查" 的方法
- 詳情視圖控制器(相冊部分內(nèi)容需要回顧加深)
- ViewController.m(在查詢時由于數(shù)據(jù)量可能較大, 因此使用GCD知識; 刪除時方法- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath要注意刪除順序)
三. 當(dāng)CoreData數(shù)據(jù)庫升級時我們需要
- 假設(shè)我們添加一個屬性, 首先我們要建立一個新版本的模型, NSManagedObject Subclass也要重新生成
- 修改DBManager.m中的- (void)createManagedContext方法
- 模擬處理country屬性
- 如果原有的數(shù)據(jù)庫文件內(nèi)有數(shù)據(jù)的話, 需要刪除原來的數(shù)據(jù)庫文件, 否則可能會報錯(可能是數(shù)據(jù)結(jié)構(gòu)問題)
一. CoreData簡介
1. CoreData是apple自己封裝的數(shù)據(jù)庫操作的一個框架, 是一種面向?qū)ο蟮姆绞絹聿僮鲾?shù)據(jù)的框架, 底層是基于SQLite的
2. 需要用到的幾個基本類型
- NSManagedObjectModel: 對應(yīng)于所有實體類的描述文件, 文件里面包含很多類的屬性和關(guān)系的描述
- NSManagedObject: 對應(yīng)于一個實體類, 所有的CoreData操作的實體類都是它的子類
- NSPersistentStoreCoordinator: 用來關(guān)聯(lián)類和對應(yīng)的數(shù)據(jù)庫文件
- NSmanagedContext: 上下文對象, 用來操作數(shù)據(jù)庫的增刪改查
二. CoreData使用
1. 圖形化操作
新建一個Core Data分類中的Data Model文件, 格式為"xcdatamodeld"
點擊左下角的Add Entity增加Entity
修改Entity名為"Computer", 為"Computer"增加2個屬性("Computer"只是測試用)
選中"Computer"這個Entity, 點擊"Editor -> Create NSManagedObject Subclass"
因為只有一個DataModel, 默認勾選狀態(tài), 直接Next即可
當(dāng)前只有一個Entity, 同樣是默認勾選狀態(tài), 直接Next即可
在Group的選項中要選擇當(dāng)前項目, 選中Target
自動創(chuàng)建了Computer類
創(chuàng)建"User"這個Entity, 為其添加4個屬性
換一種方式用"Command+N"來新建NSManagedObject Subclass
當(dāng)前有2個Entity, 我們選擇需要的"User"
創(chuàng)建完成
2. 常規(guī)的創(chuàng)建表格, 創(chuàng)建Cell
3. 核心內(nèi)容:創(chuàng)建上下文對象
//
// DBManager.m
// 01_CoreDataDemo
#import "DBManager.h"
#import <CoreData/CoreData.h>
@implementation DBManager
{
#warning 用來處理數(shù)據(jù)庫增刪改查的對象
NSManagedObjectContext *_context;
}
+ (instancetype)sharedManager
{
static DBManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[DBManager alloc] init];
});
return manager;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self createManagedContext];
}
return self;
}
// 創(chuàng)建上下文對象
- (void)createManagedContext
{
// 1. 創(chuàng)建描述文件的關(guān)聯(lián)對象
// 文件的路徑
#warning 擴展名不是寫"xcdatamodeld"
NSString *path = [[NSBundle mainBundle] pathForResource:@"MyUser" ofType:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL URLWithString:path]];
// 2. 關(guān)聯(lián)數(shù)據(jù)庫文件(SQLite)
// 先關(guān)聯(lián)數(shù)據(jù)模型描述文件對應(yīng)的對象
NSPersistentStoreCoordinator *coor = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// 再關(guān)聯(lián)數(shù)據(jù)庫
/*
第一個參數(shù): 類型(建議用SQLite類型)
第二個參數(shù): 傳nil即可
第三個參數(shù): 文件的路徑
第四個參數(shù): 選項信息
第五個參數(shù): 錯誤信息
*/
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/user.sqlite"];
NSURL *sqliteUrl = [NSURL URLWithString:filePath];
[coor addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqliteUrl options:nil error:nil];
// 3. 創(chuàng)建上下文對象, 跟上面的對象關(guān)聯(lián)起來
_context = [[NSManagedObjectContext alloc] init];
_context.persistentStoreCoordinator = coor;
}
@end
4. DBManager類中 "增刪改查" 的方法
/*
在添加數(shù)據(jù)之前, 判斷相同userId的數(shù)據(jù)是否已經(jīng)存在, 如果是, 先刪除以前存在的數(shù)據(jù), 再重新添加
*/
- (void)verifyOldUserData:(NSDictionary *)userDict
{
// 先判斷是否存在
// 查詢
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:_context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = entity;
// 設(shè)置查詢條件
if ([[userDict allKeys] containsObject:kUserId]) {
// 謂詞
NSPredicate *p = [NSPredicate predicateWithFormat:@"userId == %@", userDict[kUserId]];
request.predicate = p;
// 執(zhí)行查詢操作
NSError *error;
NSArray *array = [_context executeFetchRequest:request error:&error];
if (error) {
NSLog(@"%@", error);
}
if (array.count > 0) {
// 刪除以前的數(shù)據(jù)
for (User *tmpUser in array) {
[_context deleteObject:tmpUser];
}
// 保存(可以和上面的操作用同一個error)
[_context save:&error];
if (error) {
NSLog(@"===%@", error);
}
}
}
}
- (void)insertUser:(NSDictionary *)userDict
{
[self verifyOldUserData:userDict];
// NSEntityDescription
// 第一個參數(shù): 類名
// 第二個參數(shù): 上下文
User *user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:_context];
if ([[userDict allKeys] containsObject:kUserId]) {
user.userId = userDict[kUserId];
}
if ([[userDict allKeys] containsObject:kUserName]) {
user.userName = userDict[kUserName];
}
if ([[userDict allKeys] containsObject:kHeadImage]) {
user.headImage = userDict[kHeadImage];
}
if ([[userDict allKeys] containsObject:kAge]) {
user.age = userDict[kAge];
}
if ([[userDict allKeys] containsObject:kCountry]) {
user.country = userDict[kCountry];
}
// 存儲
NSError *error;
[_context save:&error];
if (error) {
NSLog(@"%@", error.localizedDescription);
}
}
// 查詢所有數(shù)據(jù)
- (NSArray *)searchAllUsers
{
// 創(chuàng)建一個NSEntityDescription類型的對象
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:_context];
// 查詢對象(包括查詢的數(shù)據(jù)對象所屬的類型等信息)
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = entity;
// 執(zhí)行查詢操作
NSError *error;
NSArray *array = [_context executeFetchRequest:request error:&error];
if (error) {
NSLog(@"%@", error);
}
return array;
}
// 刪除
- (void)deleteUser:(User *)user
{
[_context deleteObject:user];
// 保存
NSError *error;
BOOL flag = [_context save:&error];
if (error) {
NSLog(@"%@", error);
}
if (flag == NO) {
NSLog(@"刪除失敗");
}
}
// 修改數(shù)據(jù)
- (void)updateUser:(NSDictionary *)userDict
{
// 1. 查詢修改的對象
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:_context];
// 2. 查詢的對象
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = entity;
// 3. 查詢條件
NSPredicate *p = [NSPredicate predicateWithFormat:@"userId = %@", userDict[kUserId]];
request.predicate = p;
NSError *error;
NSArray *array = [_context executeFetchRequest:request error:&error];
if (error) {
NSLog(@"%@", error);
} else {
// 修改數(shù)據(jù)
for (User *tmpUser in array) {
// 修改屬性值
if ([[userDict allKeys] containsObject:kUserName]) {
tmpUser.userName = userDict[kUserName];
}
if ([[userDict allKeys] containsObject:kAge]) {
tmpUser.age = userDict[kAge];
}
if ([[userDict allKeys] containsObject:kHeadImage]) {
tmpUser.headImage = userDict[kHeadImage];
}
}
// 存儲
[_context save:&error];
if (error) {
NSLog(@"%@", error);
}
}
}
@end
5. 詳情視圖控制器(相冊部分內(nèi)容需要回顧加深)
//
// DetailViewController.h
// 01_CoreDataDemo
#import <UIKit/UIKit.h>
#import "User.h"
@interface DetailViewController : UIViewController
@property (nonatomic, strong) User *user;
@end
#import "DetailViewController.h"
#import "MyUtility.h"
#import "DBManager.h"
#define kUserId (@"userId")
#define kUserName (@"userName")
#define kAge (@"age")
#define kHeadImage (@"headImage")
#define kCountry (@"country")
@interface DetailViewController () <UINavigationControllerDelegate ,UIImagePickerControllerDelegate>
{
// 用戶Id
UITextField *_userIdTextField;
// 用戶名
UITextField *_userNameTextField;
// 年齡
UITextField *_ageTextField;
// 圖片
UIButton *_imageBtn;
}
@end
@implementation DetailViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
// 用戶名
UILabel *userIdLabel = [MyUtility createLabelWithFrame:CGRectMake(30, 100, 80, 30) title:@"用戶ID:" font:nil];
_userIdTextField = [MyUtility createTextField:CGRectMake(130, 100, 200, 30) placeHolder:@"請輸入用戶ID"];
[self.view addSubview:userIdLabel];
[self.view addSubview:_userIdTextField];
// 用戶名
UILabel *userNameLabel = [MyUtility createLabelWithFrame:CGRectMake(30, 150, 80, 30) title:@"用戶名:" font:nil];
_userNameTextField = [MyUtility createTextField:CGRectMake(130, 150, 200, 30) placeHolder:@"請輸入用戶名"];
[self.view addSubview:userNameLabel];
[self.view addSubview:_userNameTextField];
// 年齡
UILabel *userAgeLabel = [MyUtility createLabelWithFrame:CGRectMake(30, 200, 80, 30) title:@"年齡:" font:nil];
_ageTextField = [MyUtility createTextField:CGRectMake(130, 200, 200, 30) placeHolder:@"請輸入年齡"];
[self.view addSubview:userAgeLabel];
[self.view addSubview:_ageTextField];
// 頭像
UILabel *imageLabel = [MyUtility createLabelWithFrame:CGRectMake(30, 280, 80, 30) title:@"頭像" font:nil];
_imageBtn = [MyUtility createButtonWithFrame:CGRectMake(130, 260, 100, 100) title:nil backgroundImageName:nil target:self action:@selector(clickBtn)];
_imageBtn.layer.cornerRadius = 8;
_imageBtn.clipsToBounds = YES;
#warning 邊框
_imageBtn.layer.borderColor = [UIColor cyanColor].CGColor;
_imageBtn.layer.borderWidth = 2;
[self.view addSubview:imageLabel];
[self.view addSubview:_imageBtn];
// 保存按鈕
UIBarButtonItem *rightSaveItem = [[UIBarButtonItem alloc] initWithTitle:@"保存" style:UIBarButtonItemStylePlain target:self action:@selector(saveAction)];
self.navigationItem.rightBarButtonItem = rightSaveItem;
// 如果是修改界面, 顯示傳過來的數(shù)據(jù)
if (self.user) {
_userIdTextField.text = self.user.userId.stringValue;
_userNameTextField.text = self.user.userName;
_ageTextField.text = self.user.age.stringValue;
// headImage
[_imageBtn setBackgroundImage:[UIImage imageWithData:self.user.headImage] forState:UIControlStateNormal];
}
}
- (void)saveAction
{
NSMutableDictionary *userDict = [NSMutableDictionary dictionary];
if (_userIdTextField.text.length > 0) {
NSNumber *userId = [NSNumber numberWithInt:_userIdTextField.text.intValue];
[userDict setObject:userId forKey:kUserId];
}
if (_userNameTextField.text.length > 0) {
[userDict setObject:_userNameTextField.text forKey:kUserName];
}
if (_ageTextField.text.length > 0) {
NSNumber *userAge = [NSNumber numberWithInt:_ageTextField.text.intValue];
[userDict setObject:userAge forKey:kAge];
}
if ([_imageBtn backgroundImageForState:UIControlStateNormal]) {
// 存儲圖片
UIImage *bgImage = [_imageBtn backgroundImageForState:UIControlStateNormal];
NSData *data = UIImagePNGRepresentation(bgImage);
[userDict setObject:data forKey:kHeadImage];
}
[userDict setObject:@80 forKey:kCountry];
if (self.user) {
// 修改
[[DBManager sharedManager] updateUser:userDict];
} else {
// 存儲
[[DBManager sharedManager] insertUser:userDict];
}
}
- (void)clickBtn
{
#warning 從相冊選圖片
UIImagePickerController *ctrl = [[UIImagePickerController alloc] init];
ctrl.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
ctrl.delegate = self;
[self presentViewController:ctrl animated:YES completion:nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - UINavigationControllerDelegate ,UIImagePickerControllerDelegate代理
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
// 獲取圖片
UIImage *image = info[UIImagePickerControllerOriginalImage];
[_imageBtn setBackgroundImage:image forState:UIControlStateNormal];
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[picker dismissViewControllerAnimated:YES completion:nil];
}
@end
6. ViewController.m(在查詢時由于數(shù)據(jù)量可能較大, 因此使用GCD知識; 刪除時方法- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
要注意刪除順序)
//
// ViewController.m
// 01_CoreDataDemo
#import "ViewController.h"
#import "UserCell.h"
#import "DetailViewController.h"
#import "DBManager.h"
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
// 數(shù)據(jù)源
@property (nonatomic, strong) NSMutableArray *dataArray;
// 表格
@property (nonatomic, strong) UITableView *tableView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 創(chuàng)建表格
[self createTableView];
// 添加按鈕
UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(gotoAddPage)];
self.navigationItem.rightBarButtonItem = rightItem;
}
- (void)gotoAddPage
{
// 跳轉(zhuǎn)界面
DetailViewController *dCtrl = [[DetailViewController alloc] init];
[self.navigationController pushViewController:dCtrl animated:YES];
}
- (NSMutableArray *)dataArray
{
if (!_dataArray) {
_dataArray = [NSMutableArray array];
}
return _dataArray;
}
- (void)createTableView
{
self.automaticallyAdjustsScrollViewInsets = NO;
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, self.view.bounds.size.width, self.view.bounds.size.height - 64) style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
[self.view addSubview:_tableView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewWillAppear:(BOOL)animated
{
// 查詢所有數(shù)據(jù)
// 查詢數(shù)據(jù)量可能較大, 因此開一個線程來進行查詢
__weak ViewController *weakSelf = self;
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
NSArray *array = [[DBManager sharedManager] searchAllUsers];
weakSelf.dataArray = [NSMutableArray arrayWithArray:array];
// 回到主線程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.tableView reloadData];
});
});
}
#pragma mark - UITableView代理
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellId = @"cellId";
UserCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell == nil) {
cell = [[[NSBundle mainBundle] loadNibNamed:@"UserCell" owner:nil options:nil] lastObject];
NSLog(@"%ld", indexPath.row);
}
// 顯示數(shù)據(jù)
User *model = self.dataArray[indexPath.row];
NSLog(@"%@", model.country);
[cell config:model];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 80;
}
// 刪除
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
#warning 刪除數(shù)據(jù)的順序(從數(shù)據(jù)庫刪除和數(shù)據(jù)源刪除的順序不能調(diào)換)
// 從數(shù)據(jù)庫刪除
User *user = self.dataArray[indexPath.row];
[[DBManager sharedManager] deleteUser:user];
// 數(shù)據(jù)源刪除
[self.dataArray removeObjectAtIndex:indexPath.row];
// UI刪除
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
// 修改
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *dCtrl = [[DetailViewController alloc] init];
dCtrl.user = self.dataArray[indexPath.row];
[self.navigationController pushViewController:dCtrl animated:YES];
}
@end
三. 當(dāng)CoreData數(shù)據(jù)庫升級時我們需要
首先要明白的是升級的含義:
升級的意思是數(shù)據(jù)庫里面的類增加了或者類的屬性添加或修改了
升級是當(dāng)前需要發(fā)布的版本跟AppStore線上版本比較而言的
1. 假設(shè)我們添加一個屬性, 首先我們要建立一個新版本的模型, NSManagedObject Subclass也要重新生成
選中, 點擊Editor -> Add Model Version
更改當(dāng)前的Model Version
2. 修改DBManager.m中的- (void)createManagedContext
方法
// 創(chuàng)建上下文對象
- (void)createManagedContext
{
// 1. 創(chuàng)建描述文件的關(guān)聯(lián)對象
// 文件的路徑
…………………………………………………………………………………………
// 2. 關(guān)聯(lián)數(shù)據(jù)庫文件(SQLite)
…………………………………………………………………………………………
/*
- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType configuration:(NSString *)configuration URL:(NSURL *)storeURL options:(NSDictionary *)options error:(NSError **)error;
方法中的options在CoreData數(shù)據(jù)庫升級的時候使用
升級的意思是數(shù)據(jù)庫里面的類增加了或者類的屬性添加或修改了
升級是當(dāng)前需要發(fā)布的版本跟AppStore線上版本比較而言的
*/
NSDictionary *dict = @{NSMigratePersistentStoresAutomaticallyOption:[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption:[NSNumber numberWithBool:YES]};
[coor addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqliteUrl options:dict error:nil];
// 3. 創(chuàng)建上下文對象, 跟上面的對象關(guān)聯(lián)起來
…………………………………………………………………………………………
}