iOS-CoreData详解与使用

序言

在了解CoreData,大家有必要了解对象关系映射(英语称object Relational Mapping,简称ORM)。

ORM

ORM是通过使用描述对象和数据库之间映射的元数据,可以实现将对象自动持久化到关系数据库当中。ORM的存在为了解决面向对象与数据库科恩干存在不匹配的一种技术。

一 初识CoreData

1.CoreData是一种在iOS 3系统中,也是苹果自己推出的数据存储框架,采用了一种ORM(对象关系映射)的存储关系。

CoreData一个比较大的优势在于在使用CoreData过程中不需要我们编写SQL语句,也就是将OC对象存储于数据库,也可以将数据库数据转为OC对象(数据库数据与OC对象相互转换)。

2.CoreData几个类

(1)NSManagedObjectContext

托管对象上下文,数据库的大多数操作是在这个类操作

(2)NSManagedObjectModel

托管对象模型,其中一个托管对象模型关联到一个模型文件,里面存储着数据库的数据结构。

(3)NSPersistentStoreCoordinator

持久化存储协调器,主要负责协调上下文玉存储的区域的关系。

(4)NSManagedObject

托管对象类,其中CoreData里面的托管对象都会继承此类。

三 CoreData基本使用
iOS-CoreData详解与使用_第1张图片
1.gif

下面开始讲解CoreData的基本使用,里面会涉及到源码,内容比较多,希望大家静下来看完。

使用CoreData方式,有两种可能。第一种是项目开始就创建带有CoreData数据库,还有一种项目已经开始了,重新接入CoreData,下面我们第三部分主要讲述这两种方式的过程。

1.项目开始就使用CoreData

我们在创建项目的时候,勾选Use Core Data

iOS-CoreData详解与使用_第2张图片
image.png

然后在文件目录中自动生成一个后缀为.xcdatamodeld的文件

iOS-CoreData详解与使用_第3张图片
image.png

打开AppDelegate发现类中多了以下内容

  • AppDelegate.h
@property (readonly, strong) NSPersistentContainer *persistentContainer;

- (void)saveContext;
  • AppDelegate.m
@synthesize persistentContainer = _persistentContainer;

- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"CoreDataDemo"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    // Replace this implementation with code to handle the error appropriately.
                    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                    
                    /*
                     Typical reasons for an error here include:
                     * The parent directory does not exist, cannot be created, or disallows writing.
                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                     * The device is out of space.
                     * The store could not be migrated to the current model version.
                     Check the error message to determine what the actual problem was.
                    */
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    
    return _persistentContainer;
}

#pragma mark - Core Data Saving support

- (void)saveContext {
    NSManagedObjectContext *context = self.persistentContainer.viewContext;
    NSError *error = nil;
    
    if ([context hasChanges] && ![context save:&error]) {
        NSLog(@"Unresolved error %@, %@", error, error.userInfo);
        abort();
    }
}

我们可以点开testCoreData.xcdatamodeld文件,我们可以看到实体和关系。如下图

iOS-CoreData详解与使用_第4张图片
image.png

图表说明

  • Add Entity 添加表
  • Add Attrubte 添加属性

(1)
iOS-CoreData详解与使用_第5张图片
image.png

创建后可以清楚的看到模型文件左侧的列表,有三个Entities、Fetch Requests以及Configurations三个选项。

  • ENTITIES 实体
  • FETCH REQUESTS 请求模版
  • CONFIGURATIONS 配置信息

(2)
iOS-CoreData详解与使用_第6张图片
image.png

添加完一个实体后,你会发现一个实体是对应着三个内容,分别是Attributes、Relationships和Fetched Properties。

  • Attributes 属性
  • Relationships 关联关系
  • Fetched Properties 获取操作

(3)实体属性类型

iOS-CoreData详解与使用_第7张图片
image.png

我们来分别简单解释类型的意义,从上往下

  • Undefined:也就是默认值,如果参与编译会报错
  • Integer 16:代表整数,范围是-32768 ~ 32767
  • Integer 32:代表整数,范围是-2147483648 ~ 2147483647
  • Integer 64:代表整数,范围是–9223372036854775808 ~ 9223372036854775807,还是很大的,较少用
  • Double:代表小数
  • Float:代表小数
  • String:代表字符串,NSString表示
  • Boolean:代表布尔值,使用NSNumber表示
  • Date:代表日期时期
  • Binary Data:代表二进制,是用NSData表示
  • Transformable:代表Objective对象,要遵守NSCoding协议

(4)关联关系

iOS-CoreData详解与使用_第8张图片
image.png

点击加号,可以添加关联关系,在inverse这个属性代表两个实体在Relationships设置关联关系后之后,是否可以从一个实体中找到另一个实体,这样使两个实体具有双向的关联关系。

(5)Editor Style

iOS-CoreData详解与使用_第9张图片
image.png

大家通过点击下面红色按钮,style按钮可以看出实体和属性的关系,以及可以看出实体之间的对应的关系。

上面是coreData的视图的基本运用,自己也是一个不断摸索的过程,下面讲述CoreData的基本操作。

三 CoreData基本使用

在讲述操作之前,我们首先讲述NSManagedObjectContext,苹果推荐使用initWithConcurrencyType方式创建,在创建时,指定当前是什么类型的并发队列,参数也是一个枚举值。

NSManagedObjectContext枚举值参数有三个类型:

  • NSConfinementConcurrencyType:此类型在iOS9之后被苹果弃用,所以不建议用这个API。
  • NSPrivateQueueConcurrencyType:代表私有并发队列的类型,操作也是在子线程中完成的。
  • NSMainQueueConcurrencyType:代表主并发队列类型,如果在操作过程中,需要涉及到UI操作,则应该使用这个参数初始化上下文完成操作。

创建NSManagedObjectContext一共有两种方法,下面分别介绍

1.下面我们以Student为模型,创建主队列并发类型的NSManagedObjectContext
/** NSManagedObjectContext */
@property(nonatomic, strong)NSManagedObjectContext *context;

- (NSManagedObjectContext *)context {
    if (_context == nil) {
        // 创建上下文对象,并发队列设置为主队列
        _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        
        // 创建托管对象模型,并使用Student.momd路径当做初始化参数
        // .xcdatamodeld文件 编译之后变成.momd文件  (.mom文件)
        NSURL *modelPath = [[NSBundle mainBundle] URLForResource:@"CoreDataDemo" withExtension:@"momd"];
        NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelPath];
        
        // 创建持久化存储调度器
        NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
        
        // 创建并关联SQLite数据库文件,如果已经存在则不会重复创建
        NSString *dataPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
        dataPath = [dataPath stringByAppendingFormat:@"/%@.sqlite",@"Student"];
        
        [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:nil error:nil];
        
        // 上下文对象设置属性为持久化存储器
        _context.persistentStoreCoordinator = coordinator;
    }
    return _context;
}
  • 解释说明

(1) 将NSManagedObjectContext声明为一个变量,并且使用懒加载的形式,方便使用。

(2) .xcdatamodeld文件 编译之后变成.momd文件,所以生成NSManagedObjectModel托管对象模型的时候,文件后缀名一定要写对。

iOS-CoreData详解与使用_第10张图片
image.png

(3) 创建并关联SQLite数据库文件,如果已经存在则不会重复创建。


iOS-CoreData详解与使用_第11张图片
image.png
2.使用系统推荐的方法创建
@property (readonly, strong) NSPersistentContainer *persistentContainer;

@synthesize persistentContainer = _persistentContainer;

- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"CoreDataDemo"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    
    return _persistentContainer;
}

使用的时候

self.persistentContainer.viewContext
四 实战 - 增,删,改,查
4.1 插入操作

CoreData通过NSEntityDescriptioninsert进行插入操作,这样就会生成并返回一个托管对象,并将这个对象插入到上下文中。下面以一个Student为例:

- (Student *)insert1 {
    NSError *error = nil;
    
   // 开始创建托管对象,并指明好创建的托管对象所属实体名
    Student * student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context];
    
    //2.根据表Student中的键值,给NSManagedObject对象赋值
    student.name = [NSString stringWithFormat:@"Mr-%d",arc4random() % 100];
    student.age = arc4random() % 20;
    student.sex = arc4random() % 2 == 0 ?  @"男" : @"女" ;
    student.height = arc4random() % 180;
    student.number = arc4random() % 100;
    
    if ([self.context hasChanges] && ![self.context save:&error]) {
        NSLog(@"Unresolved error %@, %@", error, error.userInfo);
        _resultLbe.text = [NSString stringWithFormat:@"CoreData Error:%@",error.userInfo];
        abort();
        return nil;
    } else {
        _resultLbe.text = @"插入数据成功";
    }
    
    return student;
}

NSManagedObjectContext将操作的数据放到了缓存层中,只有调用了NSManagedObjectContextsave后,才会对数据库进行真正的操作,否则对象仅仅存在内存中,这样就很好地避免了数据库的频繁访问。

4.2 删除操作

CoreData首先通过获取需要删除的托管对象,遍历所需要获取的对象数组,逐个删除,最后调用NSManagedObjectContextsave方法。

- (void)delete:(NSArray *)delStudents {
    // 获取数据的请求对象,指明对实体进行删除操作
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    NSMutableArray *delStudentSucces = [NSMutableArray array];  // 保存在数据库中成功被删除的对象
    [delStudents enumerateObjectsUsingBlock:^(Student *obj, NSUInteger idx, BOOL *stop) {
        // 通过创建谓词对象,然后过滤掉符合要求的对象,也就是要删除的对象
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"number = %d",obj.number];
        request.predicate = predicate;
        
        // 通过执行获取操作,找到要删除的对象即可
        NSError *error = nil;
        NSArray *students = [self.context executeFetchRequest:request error:&error];
        
        // 开始真正操作,一一遍历,遍历符合删除要求的对象数组,执行删除操作
        [students enumerateObjectsUsingBlock:^(Student *obj, NSUInteger idx, BOOL *stop) {
            [self.context deleteObject:obj];
        }];
        
        // 错误处理
        if (error) {
            self.resultLbe.text = [NSString stringWithFormat:@"CoreData Error:%@",error.description];
        } else {
            [delStudentSucces addObject:obj];
        }
    }];
    
    // 最后保存数据,保存上下文。
    if (self.context.hasChanges) {
        [self.context save:nil];
    }
    
    if (delStudentSucces.count > 0) {
        self.resultLbe.text = @"删除数据成功";
    }
    
    // 将已经在数据库中被删除的对象从内存中移除
    [delStudentSucces enumerateObjectsUsingBlock:^(Student *obj, NSUInteger idx, BOOL *stop) {
        [self.dataSources removeObject:obj];
    }];
    [self.tableView reloadData];
}
4.3 修改操作

将所有数据库中的学生,性别颠倒,年龄加1操作。

- (void)modify:(NSArray *)modifyStudents {
    // 获取数据的请求对象,指明对实体进行删除操作
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    NSMutableArray *modifyStudentSucces = [NSMutableArray array];
    [modifyStudents enumerateObjectsUsingBlock:^(Student *obj, NSUInteger idx, BOOL *stop) {
        // 通过创建谓词对象,然后过滤掉符合要求的对象,也就是要删除的对象
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"number = %d",obj.number];
        request.predicate = predicate;
        
        // 通过执行获取操作,找到要删除的对象即可
        NSError *error = nil;
        NSArray *students = [self.context executeFetchRequest:request error:&error];
        
        // 开始真正操作,一一遍历,遍历符合删除要求的对象数组,执行删除操作
        [students enumerateObjectsUsingBlock:^(Student *obj, NSUInteger idx, BOOL *stop) {
            obj.age += 1;
            obj.sex = [obj.sex isEqualToString:@"男"] ? @"女" : @"男";
        }];
        
        // 错误处理
        if (error) {
            self.resultLbe.text = [NSString stringWithFormat:@"CoreData Error:%@",error.description];
        } else {
            [modifyStudentSucces addObject:obj];
        }
    }];
    
    // 最后保存数据,保存上下文。
    if (self.context.hasChanges) {
        [self.context save:nil];
    }
    
    if (modifyStudentSucces.count > 0) {
        self.resultLbe.text = @"修改数据成功";
    }
    
    NSArray *news = [self search];
    [self.dataSources removeAllObjects];
    [self.dataSources addObjectsFromArray:news];
    [self.tableView reloadData];
    
    self.resultLbe.text = @"修改数据成功";
}
4.4 查找操作

查找操作是是有许多条件限制,根据条件查找出相应的数据,下面以一个例子说明一下(查找出所有的元素,条件以后细节会讲出)

- (NSArray *)search {
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    // 执行获取操作,获取所有Student托管对象
    NSError *error = nil;
    NSArray *students = [self.context executeFetchRequest:request error:&error];
    
    if (error) {
        _resultLbe.text = [NSString stringWithFormat:@"CoreData Error:%@",error.description];
    } else {
        _resultLbe.text = @"查找数据成功";
    }
    
    return students;
}

以上就是CoreData的基本使用,自己也在不断的完善中,希望上面对大家对CoreData认识会进一步提高。


本文参考 iOS-CoreData详解与使用 ,非常感谢该作者。


更多同类型文章参考
iOS-SQLite3的使用详解
iOS-CoreData详解与使用
iOS-FMDB详解及使用
iOS SQLite、CoreData、FMDB数据库详解


项目连接地址 - CoreDataDemo

你可能感兴趣的:(iOS-CoreData详解与使用)