iOS数据持久化的基本方式

status
category
date
summary
slug
icon
tags
password
iOS有多种数据持久化的方式,按照存储类型可以分为:基本类型存储,系统对象存储,自定义对象存储。其中,自定义对象存储的主要步骤,分为数据转化和数据存取两步。要将对象存储在磁盘,首先需要将其转换为二进制数据,这一步叫做序列化(Serialization),或者成为归档(Archive)。相反,将二进制数据转换成对象,则称为反序列化,或者反归档。其次是考虑二进制数据的保存和读取。

从序列化说起

有一定Android开发经验的同学一般对序列化的概念比较熟悉,在使用Intent传递数据的时候,需要实现SerializableParcelable接口。其实序列化这个名词非常形象,对象的组成虽然对程序员比较友好,但是它所包含的各种属性甚至指向其他对象的指针,在内存中的排布并无规律,该如何保存呢?序列化就是规定了所有字节排列成二进制文件的规律,相当于规定了某种文件格式,存取的时候可以按照这个格式保存对象。
前面提到的系统对象,包括NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary,都可以直接调用writeToFile写入文件,因为它们本身已经实现了NSCoding协议。所以自定义的对象想要完成序列化,首先要实现NSCoding协议,实现encode方法和init方法,分别用于序列化和反序列化。在方法中指定各属性具体如何作为键值对保存起来:
在iOS 12.0之后的Foundation框架中,NSCoder只有一个具体子类NSKeyedArchiverNSCoding协议提供的方法规定了对象属性如何以键值对的形式保存,接着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中保存的内容。
notion image
Keychain中的数据虽然和键值对相似,但和Dictionary稍有不同:Keychain中保存的每一条信息称为KeychainItem,每个Item是一个Dictionary,保存着Data(被加密的数据)和Attributes(多个Attribute,用于标记Item的类型),如下图所示:
notion image
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
画了一张图来帮助理解:
notion image
在示例中,我们创建一个Core Data文件,类型为Data Model
notion image
为了避免与Person混淆,创建一个名为CorePersonEntity,同样拥有nameage两个属性。Entity代表一个对象,相当于数据库中的一张表,对象的属性叫Attribute,即存在表里的数据。
notion image
接下来我们创建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文件,然后添加几个值:
notion image
用文本编辑打开,可以看到它的格式:
接下来从代码中读取,使用时传入文件名即可:
plist文件存储数据的优点:
  • 读写容易,且xml的格式本身具有可读性
  • 适合保存项目的配置、环境变量等等,比保存在类中更恰当
缺点:
  • 只适合储存少数类型的小体积数据
  • 涉及到路径的字符串使用,且xml内部也都是字符串解析,容易出错

文件

虽然所有数据持久化的本质,都是依赖于文件写入磁盘,但是上次直接读写文件,还是大一时候学习C语言文件指针,所以觉得这种方式十分古老,使用场景也不多,就放在最后介绍它。尽管如此,苹果还是对文件读写非常友好,主要原因是沙盒的存在似的应用的文件结构比较清晰,以及FileManager的API非常简单。
我们对FileManager做一个简单的包装:
可以看到直接与文件进行转化的是Data类型,这就回到了序列化那一套流程。文件作为经典的数据储存方式,优点主要靠FileManager提供:
  • FileManager支持iCloud同步
  • FileManager线程安全
  • 适合储存体积非常大的数据
缺点:
  • URL和字符串的拼接非常容易出错
  • 受沙盒影响,应用的数据与应用绑定
  • IO操作非常非常慢
虽然文件和其他方式相比有些原始,但持久化并不只是保存一些对象信息,加上现在的音视频图片存储,文件操作依然非常重要。

总结

本文对一般场景的数据持久化方式做了简单介绍,并没有涵盖深入实践和复杂用法,主要目的在于区分各种方式的不同,以及确定不同场景下最合适的方案,因此以表格方式做一个总结。
方案
特点
数据类型
数据体积
使用场景举例
UserDefaults
用户偏好
自定义
主题字体、是否自动登录
Keychain
安全
自定义
密码、加密数据
Core Data(数据库)
具有redo/undo能力
自定义
数据列表缓存
Property List
不经常改动
系统对象
应用名称、版本号、省市列表
文件
缓存、安全
自定义
音视频、图片缓存
参考资料:
Loading...

© 刘口子 2018-2025