数据存储

本文将对以下几个模块进行叙述。

  1. 沙盒
  2. Plist
  3. Preference偏好设置
  4. NSKeyedArchiver归档 / NSKeyedUnarchiver解档
  5. SQLite3的使用
  6. FMDB
  7. Core Data

Realm的文档很详细,这篇文章就不写了: Realm的使用方法

下图是Core Data堆栈的图示,在这里是为了做文章的封面图片,后文会介绍Core Data的使用方法。

Core Data

一、沙盒

iOS本地化存储的数据保存在沙盒中, 并且每个应用的沙盒是相对独立的。每个应用的沙盒文件结构都是相同的,如下图所示:
沙盒目录

Documents:iTunes会备份该目录。一般用来存储需要持久化的数据。

Library/Caches:缓存,iTunes不会备份该目录。内存不足时会被清除,应用没有运行时,可能会被清除,。一般存储体积大、不需要备份的非重要数据。

Library/Preference:iTunes同会备份该目录,可以用来存储一些偏好设置。

tmp: iTunes不会备份这个目录,用来保存临时数据,应用退出时会清除该目录下的数据。

如何拿到每个文件夹的路径呢?我这里就只说最好的一种方法。

上面的方法有三个参数,这里分别说一下。

NSDocumentDirectory: 第一个参数代表要查找哪个文件,是一个枚举,点进去看一下发现有很多选择,为了直接找到沙盒中的Documents目录,我们一般用NSDocumentDirectory。

NSUserDomainMask: 也是一个枚举,表示搜索的范围限制于当前应用的沙盒目录。我们一般就选择NSUserDomainMask。

YES (expandTilde): 第三个参数是一个BOOL值。iOS中主目录的全写形式是/User/userName,这个参数填YES就表示全写,填NO就是写成‘‘~’’,我们一般填YES。

根据上面的文字,你应用可以知道如何拿到Library/Caches目录下的文件路径了吧?没错,就是这样:

所以,如果程序中有需要长时间持久化的数据,就选择Documents,如果有体积大但是并不重要的数据,就可以选择交给Library,而临时没用的数据当然是放到temp。至于Preference则可以用来保存一些设置类信息,后面会讲到偏好设置的使用方法。

推荐一个好用的分类工具集合WHKit,可以直接使用分类方法拿到文件路径。

二、Plist存储

Plist文件的Type可以是字典NSDictionary或数组NSArray,也就是说可以把字典或数组直接写入到文件中。
NSString、NSData、NSNumber等类型,也可以使用writeToFile:atomically:方法直接将对象写入文件中,只是Type为空。

下面就举个例子来看一下如何使用Plist来存储数据。

上面的代码运行之后,在应用沙盒的Documents中就创建了一个plist文件,并且已经写入数据保存。
写入plist

数据存储了,那么如何读取呢?

上面这段代码就读出了plist种的数据。

三、Preference偏好设置

偏好设置的使用非常方便快捷,我们一般使用它来进行一些设置的记录,比如用户名,开关是否打开等设置。
Preference是通过NSUserDefaults来使用的,是通过键值对的方式记录设置。下面举个例子。

利用NSUserDefaults判断APP是不是首次启动。

通过键值对的方式非常easy的保存了数据。

注意:NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存储其他类型,则需要转换为前面的类型,才能用NSUserDefaults存储。

下面的例子是用NSUserDefaults存储图片,需要先把图片转换成NSData类型。

NSUserDefaults是不是很好用!不过有没有觉得每次都写[NSUserDefaults standardUserDefaults]有点烦,那么又到了推荐环节,好用的分类工具WHKit,这个工具中有许多好用的宏定义,其中一个就是KUSERDEFAULT,等同于[NSUserDefaults standardUserDefaults]。
WHKit中还有更多好用的宏定义等着你!

四、NSKeyedArchiver归档 / NSKeyedUnarchiver解档

归档和解档会在写入、读出数据之前进行序列化、反序列化,数据的安全性相对高一些。
NSKeyedArchiver可以有三个使用情景。

1.对单个简单对象进行归档/解档

与plist差不多,对于简单的数据进行归档,直接写入文件路径。

当然,也可以存储NSArray,NSDictionary等对象。

2.对多个对象进行归档/解档

这种情况可以一次保存多种不同类型的数据,最终使用的是与plist相同的writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile来写入数据。

3.归档保存自定义对象

我认为这是归档最常使用的情景。
定义一个Person类,如果想对person进行归档解档,首先要让Person遵守协议。

NSCoding协议有2个方法:

  • (void)encodeWithCoder:(NSCoder *)aCoder
    归档时调用这个方法,在方法中使用encodeObject:forKey:归档变量。
  • (instancetype)initWithCoder:(NSCoder *)aDecoder
    解档时调用这个方法,在方法中石油decodeObject:forKey读出变量。

下面就可以在需要的地方归档或解档Person对象。

五、SQLite3的使用

1、首先需要添加库文件libsqlite3.0.tbd
2、导入头文件#import <sqlite3.h>
3、打开数据库
4、创建表
5、对数据表进行增删改查操作
6、关闭数据库

上代码之前,有些问题你需要了解。

  • SQLite 不区分大小写,但也有需要注意的地方,例如GLOB 和 glob 具有不同作用。

  • SQLite3有5种基本数据类型 text、integer、float、boolean、blob

  • SQLite3是无类型的,在创建的时候你可以不声明字段的类型,不过还是建议加上数据类型

下面的代码就是SQLite3的基本使用方法,带有详细注释。
代码中用到了一个Student类,这个类有两个属性name和age。

附上SQLite的基本语句
– 创建表:
create table if not exists 表名 (字段名1, 字段名2…);

create table if not exists t_student (id integer primary key autoincrement, name text not null, age integer)
– 增加数据:
insert into 表名 (字段名1, 字段名2, …) values(字段1的值, 字段2的值, …);
insert into t_student (name,age) values (@”Jack”,@17);
– 根据条件删除数据:
delete from 表名 where 条件;
delete from t_student where name = @”Jack”;
– 删除表中所有的数据:
delete from 表名
delete from t_student
– 根据条件更改某个数据:
update 表名 set 字段1 = ‘值1’, 字段2 = ‘值2’ where 字段1 = ‘字段1的当前值’
update t_student set name = ‘lily’, age = ’16’ where name = ‘Jack’
– 根据条件查找:
select * from 表名 where 字段1 = ‘字段1的值’
select * from t_student where age = ’16’
– 查找所有数据:
select * from 表名
select * from t_student
– 删除表:
drop table 表名
drop table t_student
– 排序查找:
select * from 表名 order by 字段
select * from t_student order by age asc (升序,默认)
select * from t_student order by age desc (降序)
– 限制:
select * from 表名 limit 值1, 值2
select * from t_student limit 5, 10 (跳过5个,一共取10个数据)

SQLite3还有事务方面的使用,这里就不做说明了,下面的FMDB中会有事务的使用。

六、FMDB

FMDB封装了SQLite的C语言API,更加面向对象。
首先需要明确的是FMDB中的三个类。

FMDatabase:可以理解成一个数据库。

FMResultSet:查询的结果集合。

FMDatabaseQueue:运用多线程,可执行多个查询、更新。线程安全。

FMDB基本语法

查询:executeQuery: SQLite语句命令。

其余的操作都是“更新”:executeUpdate: SQLite语句命令。

FMDB的基本使用

在项目中导入FMDB框架和sqlite3.0.tbd,导入头文件。

1. 打开数据库,并创建表

初始化FMDatabase:FMDatabase *db = [FMDatabase databaseWithPath:filePath];
其中的filePath是提前准备好要存放数据的路径。

打开数据库:[db open]

创建数据表:[db executeUpdate:@”create table if not exists t_person (id integer primary key autoincrement, name text, age integer)”];

2. 插入数据

运用executeUpdate方法执行插入数据命令: [db executeUpdate:@”insert into t_person(name,age) values(?,?)”,@”jack”,@17]

3. 删除数据

删除姓名为lily的数据:[db executeUpdate:@”delete from t_person where name = ‘lily'”]

4. 修改数据

把年龄为17岁的数据,姓名改为lily:[db executeUpdate:@”update t_person set name = ‘lily’ where age = 17″]

5. 查询数据

执行查询语句,用FMResultSet接收查询结果:FMResultSet *set = [db executeQuery:@”select id, name, age from t_person”]

遍历查询结果:[set next]

拿到每条数的姓名:NSString *name = [set stringForColumnIndex:1];

也可以这样拿到每条数据的姓名:NSString *name = [result stringForColumn:@”name”];

6. 删除表

删除指定表:[db executeUpdate:@”drop table if exists t_person”]

FMDatabaseQueue基本使用

FMDatabase是线程不安全的,当FMDB数据存储想要使用多线程的时候,FMDatabaseQueue就派上用场了。

初始化FMDatabaseQueue的方法与FMDatabase类似

在FMDatabaseQueue中执行命令的时候也是非常方便,直接在一个block中进行操作

FMDB中的事务

什么是事务?

事务(Transaction)是不可分割的一个整体操作,要么都执行,要么都不执行。
举个例子,幼儿园有20位小朋友由老师组织出去春游,返校的时候,所有人依次登上校车,这时候如果有一位小朋友没有上车,车也是不能出发的。所以哪怕19人都上了车,也等于0人上车。20人是一个整体。
当然这个例子可能不是很精准。

FMDB中有事务的回滚操作,也就是说,当一个整体事务在执行的时候出了一点小问题,则执行回滚,之后这套事务中的所有操作将整体无效。

下面代码中,利用事务循环向数据库中添加2000条数据,假如在添加的过程中出现了一些问题,由于执行了*rollback = YES的回滚操作,数据库中一个数据都不会出现。
如果第2000条数据的添加出了问题,哪怕之前已经添加了1999条数据,由于执行了回滚,数据库中依然一个数据都没有。

七、Core Data

Core Data有着图形化的操作界面,并且是操作模型数据的,更加面向对象。当你了解并熟悉它的时候,相信你会喜欢上这个数据库存储框架。

利用Core Data快速实现数据存储

1. 图形化创建模型

创建项目的时候,勾选下图中的Use Core Data选项,工程中会自动创建一个数据模型文件。当然,你也可以在开发中自己手动创建。

自动创建模型文件

下图就是自动创建出来的文件

创建出来的文件

如果没有勾选,也可以在这里手动创建。

手动创建

点击Add Entity之后,相当一张数据表。表的名称自己在上方定义,注意首字母要大写。
在界面中还可以为数据实体添加属性和关联属性。

创建一个数据表

Core Data属性支持的数据类型如下

数据类型

编译之后,Xcode会自动生成Person的实体代码文件,并且文件不会显示在工程中,如果下图中右侧Codegen选择Manual/None,则Xcode就不会自动生成代码,我们可以自己手动生成。

6.png

手动生成实体类代码,选中CoreDataTest.xcdatamodeld文件,然后在Mac菜单栏中选择Editor,如下图所示。一路Next就可以了。
如果没有选择Manual/None,依然进行手动创建的话,则会与系统自动创建的文件发生冲突,这点需要注意。
你也可以不要选择Manual/None,直接使用系统创建好的NSManagedObject,同样会有4个文件,只是在工程中是看不到的,使用的时候直接导入#import “Person+CoreDataClass.h”头文件就可以了。

手动创建NSManagedObject

手动创建出来的是这样4个文件

10.png

还要注意编程语言的选择,Swift或OC

编程语言

2.Core Data堆栈的介绍与使用

下面要做的就是对Core Data进行初始化,实现本地数据的保存。
需要用到的类有三个:

NSManagedObjectModel 数据模型的结构信息
NSPersistentStoreCoordinator 数据持久层和对象模型协调器
NSManagedObjectContext 对象的上下文managedObject 模型

如下图所示,一个context内可以有多个模型对象,不过在大多数的操作中只存在一个context,并且所有的对象存在于那个context中。
对象和他们的context是相关联的,每个被管理的对象都知道自己属于哪个context,每个context都知道自己管理着哪些对象。

Core Data从系统读或写的时候,有一个持久化存储协调器(persistent store coordinator),并且这个协调器在文件系统中与SQLite数据库交互,也连接着存放模型的上下文Context。

Core Data

下图是比较常用的方式:

Core Data堆栈

下面我们来一步步实现Core Data堆栈的创建。

  • 首先在AppDelegate中定义一个NSManagedObjectModel属性。然后利用懒加载来创建NSManagedObjectModel对象。并且要注意创建时候的后缀用momd,代码如下:

  • 创建协调器NSPersistentStoreCoordinator,同样的先在AppDelegate中来一个属性,然后懒加载。

  • 创建NSManagedObjectContext,同样的是属性+懒加载。

3.运用Core Data对数据进行增删改查

  • 添加数据
    使用NSEntityDesctiption类的一个方法创建NSManagedObject对象。参数一是实体类的名字,参数二是之前创建的Context。
    为对象赋值,然后存储。

  • 查询数据
    Core Data从数据库中查询数据,会用到三个类:

NSFetchRequest:一条查询请求,相当于 SQL 中的select语句
NSPredicate:谓词,指定一些查询条件,相当于 SQL 中的where
NSSortDescriptor:指定排序规则,相当于 SQL 中的 order by

NSFetchRequest中有两个属性:

predicate:是NSPredicate对象
sortDescriptors:它是一个NSSortDescriptor数组,数组中前面的优先级比后面高。可以有多个排列规则。

设置好NSFetchRequest之后,调用NSManagedObjectContext的executeFetchRequest方法,就会返回结果集了。

Xcode自己有一个NSFetchRequest的code snippet,“fetch”,出现的结果如下图。

snippet

NSFetchRequest的code snippet: “fetch”

  • 更新数据
    更新数据比较简单,查询出来需要修改的数据之后,直接修改值,然后用context save就可以了。

  • 删除数据
    查询出来需要删除的数据之后,调用 NSManagedObjectContext 的deleteObject方法就可以了。

至此Core Data的基础内容就讲完了。


后记:

iOS中有多种数据持久化的方法,要根据具体情景选用最合适的技术。

推荐简单又好用的分类集合:WHKit
github地址:https://github.com/remember17