D32:CoreData

目录

一. CoreData简介

  1. CoreData是apple自己封装的数据库操作的一个框架, 是一种面向对象的方式来操作数据的框架, 底层是基于SQLite的
  2. 需要用到的几个基本类型

二. CoreData使用

  1. 图形化操作
  2. 常规的创建表格, 创建Cell
  3. 核心内容:创建上下文对象
  4. DBManager类中 "增删改查" 的方法
  5. 详情视图控制器(相册部分内容需要回顾加深)
  6. ViewController.m(在查询时由于数据量可能较大, 因此使用GCD知识; 删除时方法- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath要注意删除顺序)

三. 当CoreData数据库升级时我们需要

  1. 假设我们添加一个属性, 首先我们要建立一个新版本的模型, NSManagedObject Subclass也要重新生成
  2. 修改DBManager.m中的- (void)createManagedContext方法
  3. 模拟处理country属性
  4. 如果原有的数据库文件内有数据的话, 需要删除原来的数据库文件, 否则可能会报错(可能是数据结构问题)

一. CoreData简介

1. CoreData是apple自己封装的数据库操作的一个框架, 是一种面向对象的方式来操作数据的框架, 底层是基于SQLite的
2. 需要用到的几个基本类型
  1. NSManagedObjectModel: 对应于所有实体类的描述文件, 文件里面包含很多类的属性和关系的描述
  • NSManagedObject: 对应于一个实体类, 所有的CoreData操作的实体类都是它的子类
  • NSPersistentStoreCoordinator: 用来关联类和对应的数据库文件
  • NSmanagedContext: 上下文对象, 用来操作数据库的增删改查

二. CoreData使用

1. 图形化操作
新建一个Core Data分类中的Data Model文件, 格式为"xcdatamodeld"
点击左下角的Add Entity增加Entity
修改Entity名为"Computer", 为"Computer"增加2个属性("Computer"只是测试用)
选中"Computer"这个Entity, 点击"Editor -> Create NSManagedObject Subclass"
因为只有一个DataModel, 默认勾选状态, 直接Next即可
当前只有一个Entity, 同样是默认勾选状态, 直接Next即可
在Group的选项中要选择当前项目, 选中Target
自动创建了Computer类
创建"User"这个Entity, 为其添加4个属性
换一种方式用"Command+N"来新建NSManagedObject Subclass
当前有2个Entity, 我们选择需要的"User"
创建完成
2. 常规的创建表格, 创建Cell
3. 核心内容:创建上下文对象
//
//  DBManager.m
//  01_CoreDataDemo

#import "DBManager.h"
#import 

@implementation DBManager
{
#warning 用来处理数据库增删改查的对象
    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;
}

// 创建上下文对象
- (void)createManagedContext
{
    // 1. 创建描述文件的关联对象
    // 文件的路径
#warning 扩展名不是写"xcdatamodeld"
    NSString *path = [[NSBundle mainBundle] pathForResource:@"MyUser" ofType:@"momd"];
    
    NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL URLWithString:path]];
    
    // 2. 关联数据库文件(SQLite)
    // 先关联数据模型描述文件对应的对象
    NSPersistentStoreCoordinator *coor = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    // 再关联数据库
    /*
     第一个参数: 类型(建议用SQLite类型)
     第二个参数: 传nil即可
     第三个参数: 文件的路径
     第四个参数: 选项信息
     第五个参数: 错误信息
     */
    NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/user.sqlite"];
    NSURL *sqliteUrl = [NSURL URLWithString:filePath];
    
    [coor addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqliteUrl options:nil error:nil];
    
    // 3. 创建上下文对象, 跟上面的对象关联起来
    _context = [[NSManagedObjectContext alloc] init];
    _context.persistentStoreCoordinator = coor;
}

@end
4. DBManager类中 "增删改查" 的方法
/*
 在添加数据之前, 判断相同userId的数据是否已经存在, 如果是, 先删除以前存在的数据, 再重新添加
 */
- (void)verifyOldUserData:(NSDictionary *)userDict
{
    // 先判断是否存在
    // 查询
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:_context];
    
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    request.entity = entity;
    
    // 设置查询条件
    if ([[userDict allKeys] containsObject:kUserId]) {
        
        // 谓词
        NSPredicate *p = [NSPredicate predicateWithFormat:@"userId == %@", userDict[kUserId]];
        request.predicate = p;
        
        // 执行查询操作
        NSError *error;
        NSArray *array = [_context executeFetchRequest:request error:&error];
        if (error) {
            NSLog(@"%@", error);
        }
        
        if (array.count > 0) {
            // 删除以前的数据
            for (User *tmpUser in array) {
                [_context deleteObject:tmpUser];
            }
            // 保存(可以和上面的操作用同一个error)
            [_context save:&error];
            if (error) {
                NSLog(@"===%@", error);
            }
        }
    }
}

- (void)insertUser:(NSDictionary *)userDict
{
    [self verifyOldUserData:userDict];
    
    // NSEntityDescription
    // 第一个参数: 类名
    // 第二个参数: 上下文
    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);
    }
}

// 查询所有数据
- (NSArray *)searchAllUsers
{
    // 创建一个NSEntityDescription类型的对象
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:_context];
    
    // 查询对象(包括查询的数据对象所属的类型等信息)
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    
    request.entity = entity;
    
    // 执行查询操作
    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(@"删除失败");
    }
}

// 修改数据
- (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 {
        // 修改数据
        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. 详情视图控制器(相册部分内容需要回顾加深)
//
//  DetailViewController.h
//  01_CoreDataDemo

#import 
#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 () 
{
    // 用户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;
    
    // 如果是修改界面, 显示传过来的数据
    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(在查询时由于数据量可能较大, 因此使用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 () 

// 数据源
@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.

    // 创建表格
    [self createTableView];
    
    // 添加按钮
    UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(gotoAddPage)];
    self.navigationItem.rightBarButtonItem = rightItem;
}

- (void)gotoAddPage
{
    // 跳转界面
    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
{
    // 查询所有数据
    // 查询数据量可能较大, 因此开一个线程来进行查询
    __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);
    }

    // 显示数据
    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 删除数据的顺序(从数据库删除和数据源删除的顺序不能调换)
        // 从数据库删除
        User *user = self.dataArray[indexPath.row];
        [[DBManager sharedManager] deleteUser:user];
        
        // 数据源删除
        [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

三. 当CoreData数据库升级时我们需要

首先要明白的是升级的含义:

升级的意思是数据库里面的类增加了或者类的属性添加或修改了
升级是当前需要发布的版本跟AppStore线上版本比较而言的

1. 假设我们添加一个属性, 首先我们要建立一个新版本的模型, NSManagedObject Subclass也要重新生成
选中, 点击Editor -> Add Model Version
更改当前的Model Version
2. 修改DBManager.m中的- (void)createManagedContext方法
// 创建上下文对象
- (void)createManagedContext
{
    // 1. 创建描述文件的关联对象
    // 文件的路径
    …………………………………………………………………………………………  
    
    // 2. 关联数据库文件(SQLite)
    …………………………………………………………………………………………        
    
    /*
     - (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType configuration:(NSString *)configuration URL:(NSURL *)storeURL options:(NSDictionary *)options error:(NSError **)error;
     
     方法中的options在CoreData数据库升级的时候使用
     升级的意思是数据库里面的类增加了或者类的属性添加或修改了
     升级是当前需要发布的版本跟AppStore线上版本比较而言的
     */
    NSDictionary *dict = @{NSMigratePersistentStoresAutomaticallyOption:[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption:[NSNumber numberWithBool:YES]};
    [coor addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqliteUrl options:dict error:nil];
    
    // 3. 创建上下文对象, 跟上面的对象关联起来
    …………………………………………………………………………………………  
}
3. 模拟处理country属性
4. 如果原有的数据库文件内有数据的话, 需要删除原来的数据库文件, 否则可能会报错(可能是数据结构问题)

你可能感兴趣的:(D32:CoreData)