iOS数据持久化的基本方式
status
category
date
summary
slug
icon
tags
password
iOS有多种数据持久化的方式,按照存储类型可以分为:基本类型存储,系统对象存储,自定义对象存储。其中,自定义对象存储的主要步骤,分为数据转化和数据存取两步。要将对象存储在磁盘,首先需要将其转换为二进制数据,这一步叫做序列化(Serialization),或者成为归档(Archive)。相反,将二进制数据转换成对象,则称为反序列化,或者反归档。其次是考虑二进制数据的保存和读取。
从序列化说起
有一定Android开发经验的同学一般对序列化的概念比较熟悉,在使用
Intent
传递数据的时候,需要实现Serializable
或Parcelable
接口。其实序列化这个名词非常形象,对象的组成虽然对程序员比较友好,但是它所包含的各种属性甚至指向其他对象的指针,在内存中的排布并无规律,该如何保存呢?序列化就是规定了所有字节排列成二进制文件的规律,相当于规定了某种文件格式,存取的时候可以按照这个格式保存对象。前面提到的系统对象,包括
NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary
,都可以直接调用writeToFile
写入文件,因为它们本身已经实现了NSCoding
协议。所以自定义的对象想要完成序列化,首先要实现NSCoding
协议,实现encode
方法和init
方法,分别用于序列化和反序列化。在方法中指定各属性具体如何作为键值对保存起来:在iOS 12.0之后的
Foundation
框架中,NSCoder
只有一个具体子类NSKeyedArchiver
。NSCoding
协议提供的方法规定了对象属性如何以键值对的形式保存,接着NSKeyedArchiver
将保存的数据转化为二进制序列,然后就可以进行data
写入文件、传输data
等操作:(对于可能抛出异常的操作,本文一概忽略)NSKeyedArchiver
可以把数据保存为两种格式:二进制和xml
,前者占用空间小,后者具有可读性,我们可以自定义文件格式:如果在
requiringSecureCoding
传入true
,序列化的工作就只会针对实现了NSSecureCoding
协议的对象。对应的从文件获取Data
,然后利用NSKeyedUnarchiver
进行反序列化的过程非常类似,就不再赘述了。另外,
Swift 4.0
引入了Codable
协议,目标是取代NSCoding
,我们不需要编写任何代码,就可以在编译器的帮助下完成对象或者结构体的序列化。但这里为了加深理解,还是手动完成了编解码的过程。介绍完了序列化,就开始进入正题:iOS持久化的手段。
UserDefaults
从名称也可以知道,
UserDefaults
被用来储存应用或者用户的默认信息,比如账号密码、主题字体等等信息,主要是为了减少冗余操作,下次能更方便地使用App。它适合保存轻量级的本地数据,数据最终会存储到.plist
文件中。它支持了内存层面的同步读写,和持久化的异步操作,并且是以键值对的方式存储。假设现在我们要存储当前用户的用户名:这里的保存比较简陋,可以先运行一次,然后注释掉保存的代码实验一下,每次运行的时候,就会打印出已经保存在本地的“一个测试的UID”。当然,我们也可以存储自定义的对象:
我们先只调用
setPerson
方法,然后只调用getPerson
方法,重新启动APP,就可以获取到上一次存储的Person
对象了。可以看出,存储自定义对象的基本思想非常简单,主要还是需要完成序列化和反序列化的操作,即实现NSCoding
的相应方法。我们在保存的时候,还调用了synchronize
方法,表示立即同步并写入磁盘,如果不调用的话,保存的信息会不定期地更新。另外,UserDefaults
会将数据保存到同一个文件,使用同一个Key
时会覆盖原来的数据,这也提醒我们不要存储过多数据。使用UserDefaults的好处非常多:
- 使用简单
- 线程安全,并且所有线程都可以使用
- 可以在App之间共享
当然也有一些缺点:
- 使用同一Key时会覆盖数据
- 没有加密
- 使用简单也意味着被修改很简单
Keychain
Keychain
是一个加密的数据库,可以用来储存密码、证书、私密信息等。它保存的信息处于沙盒之外,也就是说用户卸载App并不会删除Keychain
中保存的内容。
Keychain
中的数据虽然和键值对相似,但和Dictionary
稍有不同:Keychain
中保存的每一条信息称为KeychainItem
,每个Item
是一个Dictionary
,保存着Data
(被加密的数据)和Attributes
(多个Attribute
,用于标记Item
的类型),如下图所示:
Keychain
默认提供的API相当冗余啰嗦,使用一些第三方库又显得过于重量级,这里对Keychain
做一个简单封装:又到了熟悉的过程,在
Person
类中添加序列化和反序列化的方法:先创建对象并存入
Keychain
,然后注释存入的代码,重启应用尝试取出:控制台将成功打印“name:Rimson age:18”
Keychain
的优点比较明显:Keychain
是iOS设备上最安全的数据持久化方式,没有之一
- 独立于APP存储,卸载后仍保留
- 线程安全
缺点也比较明显:
- API又臭又长
- 读写信息较多时,速度较慢
- 不适合存储大对象
Core Data(数据库)
Core Data
作为苹果亲儿子,不仅可以用于数据存储和缓存,而且能可视化地描述对象的结构。它的创建和使用方式也和传统的代码不太一样,Core Data
会帮我们把对象抽象出来,我们就不需要直接和数据库进行交互了。另外,Core Data
本身会记录数据的改变,因此支持了数据库undo和redo操作。Core Data
的结构也相对复杂一点,主要有三个部分组成:NSManagedObject
:数据模型,也是我们的存储对象,保存在上下文中
NSMagagedObjectContext
:位于内存之中,管理对象的上下文,保存时再进行持久化,一般对它直接操作
NSPersistentStoreCoordinator
:持久化存储的协调器,作为本地数据库和Context
之间的桥梁
还有一些相关的类和概念:
Core Data Stack
:这个Stack并没有体现传统栈的特点,它可以理解为上面几个类的管理者
NSPersistentContainer
:一个包裹了上面这些类的容器,用来创建Core Data Stack
画了一张图来帮助理解:

在示例中,我们创建一个
Core Data
文件,类型为Data Model
。
为了避免与
Person
混淆,创建一个名为CorePerson
的Entity
,同样拥有name
和age
两个属性。Entity
代表一个对象,相当于数据库中的一张表,对象的属性叫Attribute
,即存在表里的数据。
接下来我们创建
Core Data Stack
,也就是管理整个Core Data
的增删查改:可以先通过
saveCorePerson
存入几条数据,然后通过loadAllCorePerson
获取所有的数据试试效果。和所有数据库一样,Core Data
也支持其他增删查改的操作,包括各种筛选条件,在这里就不展示了。亲儿子
Core Data
的众多好处:- 使用简单,并且苹果在非常努力地维护,提供了很多方便操作的工具
- 完成了内存和磁盘的统一封装,代码简单,并且在速度和性能上表现不俗
缺点其实也称不上缺点,只是相对牺牲了一些:
- 需要注意不同线程的操作
- 添加的对象需要继承
NSManagedObject
- 有时候数据库的同步读写会出现一些未知问题
Property List
Information Proper List也就是plist文件,是一种基于XML的结构化文本文件,能够以键值对的形式存储序列化的对象。它的XML根节点是一个
Dictionary
,其中包括很多键值对。在iOS项目中,plist最广泛的用途就是Info.plist
,里面保存了应用名称、版本号等信息,在使用时也要避免创建相似名称的文件。另外,前面提到的UserDefaults
实际上也是用这种方式保存的。我们创建一个plist文件,然后添加几个值:

用文本编辑打开,可以看到它的格式:
接下来从代码中读取,使用时传入文件名即可:
plist文件存储数据的优点:
- 读写容易,且xml的格式本身具有可读性
- 适合保存项目的配置、环境变量等等,比保存在类中更恰当
缺点:
- 只适合储存少数类型的小体积数据
- 涉及到路径的字符串使用,且xml内部也都是字符串解析,容易出错
文件
虽然所有数据持久化的本质,都是依赖于文件写入磁盘,但是上次直接读写文件,还是大一时候学习C语言文件指针,所以觉得这种方式十分古老,使用场景也不多,就放在最后介绍它。尽管如此,苹果还是对文件读写非常友好,主要原因是沙盒的存在似的应用的文件结构比较清晰,以及
FileManager
的API非常简单。我们对
FileManager
做一个简单的包装:可以看到直接与文件进行转化的是
Data
类型,这就回到了序列化那一套流程。文件作为经典的数据储存方式,优点主要靠FileManager
提供:FileManager
支持iCloud同步
FileManager
线程安全
- 适合储存体积非常大的数据
缺点:
- URL和字符串的拼接非常容易出错
- 受沙盒影响,应用的数据与应用绑定
- IO操作非常非常慢
虽然文件和其他方式相比有些原始,但持久化并不只是保存一些对象信息,加上现在的音视频图片存储,文件操作依然非常重要。
总结
本文对一般场景的数据持久化方式做了简单介绍,并没有涵盖深入实践和复杂用法,主要目的在于区分各种方式的不同,以及确定不同场景下最合适的方案,因此以表格方式做一个总结。
方案 | 特点 | 数据类型 | 数据体积 | 使用场景举例 |
UserDefaults | 用户偏好 | 自定义 | 小 | 主题字体、是否自动登录 |
Keychain | 安全 | 自定义 | 小 | 密码、加密数据 |
Core Data(数据库) | 具有redo/undo能力 | 自定义 | 大 | 数据列表缓存 |
Property List | 不经常改动 | 系统对象 | 小 | 应用名称、版本号、省市列表 |
文件 | 缓存、安全 | 自定义 | 大 | 音视频、图片缓存 |
参考资料:
Loading...