数据储存

iOS 数据存储(五) -持久化 Core Data

2022-07-26  本文已影响0人  搬砖的crystal

一、简介

Core Data 是苹果官方提供的管理数据层对象的框架,它提供了对象—关系映射(ORM)的功能,即能够将 Objective-C 对象转化成数据,保存在 SQLite 数据库文件中,也能够将保存在数据库中的数据还原成 Objective-C 对象。在此数据操作期间,不需要编写任何 SQL 语句。Core Data 针对对象生命周期,以及持久化的对象图管理(object graph management)中的一些常见问题提供了解决方案。CoreData 的主要任务是负责数据更改的管理、序列化到磁盘、最小化内存占用以及查询数据。

Core Data 比 SQLite 做了更进一步的封装,SQLite 提供了数据的存储模型,并提供了一系列 API,你可以通过 API 读写数据库,去处理想要处理的数据。但是 SQLite 存储的数据和你编写代码中的数据(比如一个类的对象)并没有内置的联系,必须你自己编写代码去一一对应。
而 Core Data 却可以解决一个数据在持久化层和代码层的一一对应关系。也就是说,你处理一个对象的数据后,通过保存接口,它可以自动同步到持久化层里,而不需要你去实现额外的代码。

iOS10 中利用 NSPersistentContainer
iOS10 之前涉及 NSManagedObjectContextNSPersistentStoreCoordinatorNSManagedObjectModelNSPersistentStore 这些类。

1.Core Data Stack

说是一个对象的集合,由4个主要对象构成:

Core Data Stack,就是进行数据增删查改、保存的工作台,Apple 提供这样一个工作台,让你方便进行数据的保存。无需关心实现细节。

2.Persistent Container

NSPersistentContainer 是iOS 10、 macOS 10.12 之后才出现的新类。引入这个新类的目的之一,就是为了简化创建 Core Data Stack 这个工作台的过程。所以,在 iOS10 之前,创建 Core Data Stack 会复杂一些。

而 Persistent Container 也有另一个新类 NSPersistentStoreDescription,可以利用这个类,进行一些定制化设置,比如自定义存储路径、设置存储数据方式等(Core Data 支持 SQLite、XML、Binary、InMemory 4中方式存储数据)。

备注:iOS10中,如果利用 NSPersistentContainer 创建 Core Data Stack,预设的是 NSSQLiteStoreType 类型。并且默认打开了自动轻量化版本迁移功能(换言之,在 iOS10 之前,需要手动进行相关设置,才能打开版本迁移功能)。

3.Managed Object Context。

可以理解为是一块内存,提供了和 Managed Objects 交互的场所。也称为:The Context 或者 MOC。NSManagedObjectContext 类实例。
备注:对数据进行删除、保存、查询,都要用到 NSManagedObjectContext 类的相关方法。

4.Managed Object Model

直观点,你可以把它理解为就是 Xcode 中后缀为 xcdatamodel 的文件。在这个文件里,你可以通过非代码、可视化的方式,定义对象、对象的属性、对象之间的关系(Core Data 把对象称呼为实体、对象的属性称呼为特性)。

Managed Object Model 就是 Core Data 中用于描述实体、实体特性、实体间关系的一套方案。

它是 NSManagenObjectModel 的类实例(也可以通过纯代码实现 .xcdatamodel文件的内容)。

Entity - 实体

NSEntityDescription 类实例,用于定义一个对象。一个实体,最少要有名字和类名(如果没有设置类名,默认是 NSManagedObject类)。

Attribute - 特性

实体特性 NSAttributeDescription 类实例。就是 Entity 的特性,对应 App 中的创建类时的属性。

Relationship - 关系

实体关系 NSRelationshipDescription 类实例。用于描述 Entity 之间的关系。

5.Managed Object。

就是需要保存的数据,是 NSManagenObject 类实例。(对应 App 中的对象)

就我的理解,Managed Object 和上面提到的 Entity,本质上是同一个东西,就是你的数据对象,只不过是在可视化操作和纯代码操作中的不同称谓。

6.Persistent Store Coordinator

协调 Context 和 Persistent Store 的一个角色。NSPersistentStoreCoordinator 类实例。
如果只是对数据进行简单的增删查改,我们并不需要接触到这个类。

7.Persistent Store

可以理解为保存数据的地方。用于设置保存数据的方式、以及保存的路径等。(保存数据的方式指 SQLite、XML、Binary、InMemory 4种)。NSPersistentStore 类实例。也称为 The Store 或者 Database。

在 iOS10 之前,如果需要支持版本迁移功能,需要在创建 NSPersistentStore 类实例时,传入相应的 options 参数。而在 iOS10 中,则会自动打开版本迁移功能,并默认设置数据类型为 NSSQLiteStoreType

版本迁移,假如修改了数据模型(比如修改了 . xcdatamodel 文件:增加了实体,增加了特性等等�),为了防止使用者在更新 App 后,由于数据模型不一致导致崩溃,需要进行一定的处理,这个处理叫版本迁移。

二、使用

1.初始化 Core Data Stack

不过由于 iOS10 新引进了 NSPersistentContainer 类,然后新建项目又可以选择勾选 Core Data 与否。所以情况变得稍稍有点复杂。

这里分三种情况:1、在既有项目(只需支持iOS10)初始化 Core Data Stack;2、在既有项目(需兼容iOS8、9、10等系统)初始化 Core Data Stack;3、新建项目时直接勾选了 Core Data。

(1)在既有项目添加 Core Data 功能(只需支持iOS10)
// 我们先声明了一个NSPersistentContainer类型的属性:persistentContainer,在适合的时间调用initWithName:对其初始化
// 这里的Name参数,需要和后续创建的.xcdatamodeld模型文件名称一致。
_persistentContainer = [[NSPersistentContainer alloc] initWithName:@"Model"];
    
// 调用loadPersistentStoresWithCompletionHandler:方法,完成Core Data Stack的最中初始化。
// 如果不能初始化成功,在Block回调中打印错误,方便调试
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription * _Nonnull description, NSError * _Nullable error) {
        
    if (error != nil) {
        NSLog(@"Fail to load Core Data Stack : %@", error);
        abort();
    }
    else {
        ...
    }
}];

分为两步:

(2)在既有项目初始化 Core Data Stack(需兼容iOS8、9、10等系统)

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSInteger majorVersion = [NSProcessInfo processInfo].operatingSystemVersion.majorVersion;
        
        if (majorVersion < 10) {
            // iOS10以下的系统, 用旧有的方法初始化Core Data Stack
            [self initializeCoreDataLessThaniOS10];
        }
        else {
            // iOS10的系统, 用新的方法(详见上面介绍的情况1)
            [self initializeCoreData];
        }
    }
    return self;
}


- (void)initializeCoreDataLessThaniOS10 {
    // Get managed object model(拿到模型文件,也就是.xcdatamodeld文件(我们会在初始化完Core data Stack后创建))
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
    NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    NSAssert(mom != nil, @"Error initalizing Managed Object Model");
    
    // Create persistent store coordinator(创建NSPersistentStoreCoordinator对象(需要传入上述创建的NSManagedObjectModel对象))
    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
    
    // Creat managed object context(创建NSManagedObjectContext对象(_context是声明在.h文件的属性——因为其他类也要用到这个属性))
    _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    
    // assgin persistent store coordinator(赋值persistentStoreCoordinator)
    _context.persistentStoreCoordinator = psc;
    
    // Create .sqlite file(在沙盒中创建.sqlite文件)
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *documentsURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    NSURL *storeURL = [documentsURL URLByAppendingPathComponent:@"DataModel.sqlite"];
    
    // Create persistent store(异步创建NSPersistentStore并add到NSPersistentStoreCoordinator对象中,作用是设置保存的数据类型(NSSQLiteStoreType)、保存路径、是否支持版本迁移等)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        // 用于支持版本迁移的参数
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                                 [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
        NSError *error = nil;
        NSPersistentStoreCoordinator *psc = _context.persistentStoreCoordinator;
        
        // 备注,如果options参数传nil,表示不支持版本迁移
        NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType
                                                     configuration:nil
                                                               URL:storeURL
                                                           options:options
                                                             error:&error];
        NSAssert(store != nil, @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);
    });
}
(3)创建项目直接勾选 Use Core Data

项目模版会在 AppDelegate 类中直接帮你初始化好 Core Data Stack。

2.创建 managed object model

选择 Core Data 栏目下的 Data Model ,就可以创建一个 .xcdatamodeld 模型文件。
自己创建的默认系统提供的命名为 Model.xcdatamodeld
添加实体、实体的特性、关系:

(1)Entity

当在 Xcode 中点击 Model.xcdatamodeld 时,会看到编辑视图 Add Entity


如果把数据模型文件比作数据库中的“库”,那么 Entity 就相当于库里的实体。
假设我这个数据模型是用来存放图书馆信息的,那么很自然的,我会想建立一个叫 Book 的 Entity。
(2) Attributes

当建立一个名为 Book 的 Entity 时,会看到视图中有栏写着 Attributes,我们知道,当我们定义一本书时,自然要定义书名,书的编码等信息。这部分信息叫 Attributes,即书的属性。


(3) Relationship

在我们使用 Entity 编辑时,除了看到了Attributes 一栏,还看到下面有 Relationships 一栏

在 Reader 的 Relationship 下点击 + 号键。然后在Relationship 栏的名字上填 borrow,表示读者和书的关系是“借阅”,在 Destination 栏选择 Book,这样,读者和书籍的关系就确立了。
对于第三栏,Inverse,需要创建 Book 的 Relationship 后在关联。

选择 Book 的一栏,在 Relationship 下添加新的 borrowBy,Destination 是 Reader,这时候点击 Inverse 一栏,会发现弹出了 borrow,直接点上。

那么在 Reader 的 Relationship 中,我们会发现 Inverse 一栏会自动补齐为 borrowBy。因为电脑这时候已经完全理解了双方的关系,自动做了补齐。

一对一和一对多 - to one和to many

我们建立 Reader 和 Book 之间的联系的时候,发现他们的联系逻辑之间还漏了一个环节。
假设一本书被一个读者借走了,它就不能被另一个读者借走,而当一个读者借书时,却可以借很多本书。
也就是说,一本书只能对应一个读者,而一个读者却可以对应多本书。
这就是 一对一 → to one 和 一对多 → to many 。


3.创建 NSManagedObject 子类
(1)方法1

直接 Command + N 创建一个新类,继承 NSManagedObject 类,然后定义的属性和模型文件中的一致。

(2)方法2

选中对应的实体,然后 Editor > Create NSManagedObject Subclass...,系统自动生成 NSManagedObject 子类。
这种方法,如果有一对多的关系,会生成 2 个Category(Core Data生成的 NSManagedObject 子类,都是以 Category 形式存在的)
CoreDataProperties:生成实体中 Attributes 对应的属性。Relationships 也会生成对应的属性:一对多关系是 NSSet/NSOrderSet 类型属性(本质是个集合),一对一关系则是非集合的对象类型属性。
CoreDataGeneratedAccessors 其实就是一系列增加、删除 NSOrderSet/NSSet里元素的方法。(如果没有对多关系,不会有这个 Category)

上一篇下一篇

猜你喜欢

热点阅读