iOS 数据库探究

本文记录前段时间调研 iOS 移动数据库的相关知识,主要从数据库的设计及数据库的选取两方面来谈。希望阅读此篇文章后读者能够大致了解移动端数据库的相关内容。本文会不定期更新,有错误请及时指正。

一. 数据库分类

首先简单介绍下市面上的具有代表性的几个数据库:

  1. SQLite :

    SQLite 是遵守ACID的关系数据库管理系统,它包含在一个相对小的 C 程式库中。与许多其它数据库管理系统不同,SQLite 不是一个客户端/服务器结构的数据库引擎,而是被集成在用户程序中。

    SQLite 遵守 ACID,实现了大多数SQL标准。它使用动态的、弱类型的SQL语法。

    SQLite 是移动端使用最广泛的嵌入式数据库。

  2. FMDB :

    FMDB 是一个轻量级 SQLite 封装库。由于原生 SQLite API 进行数据存储需要使用 C 语言中的函数,操作较复杂,FMDB 则提供了简洁、易用的 API。FMDB 可看作 SQLite 的一个接口封装,是轻量级的,且不提供对象型的数据操作方式。

  3. FMDB 衍生类库 :

    由于 FMDB 功能有限,且不具有 ORM(对象关系映射)特性,因此出现很多基于 FMDB 之上封装的对象型第三方库,提供对象型的数据操作功能。Github 上有一些高星类库,这里不再细说。

  4. WCDB :

    WCDB 是微信团队出品的一个基于 SQLite 的数据库框架。WCDB 具有很多特性,如 WINQ语言、SQLite 采用多线程模式、数据库加密、数据库修复、反注入等等。其使用 C++ 对底层进行了较多的修改。

  5. Core Data

    Core Data 是苹果提供的对象型数据库框架,虽然是官方出品,但由于其复杂性应用并不广泛,相关文章可参考《Core Data 概述》,《我为什么不喜欢 Core Data》

  6. Realm :

    Realm 是由 Y Combinator 孵化的创业团队开源出来的一款可以用于iOS (同样适用于 Swift & Objective-C ) 和 Android 的跨平台移动数据库。其重新设计了数据库核心引擎,定位为高性能、跨平台、简单易用的 ORM 型数据库。Relam 提供了独有的 API 来进行相关数据操作。

  7. CTPersistance

    CTPersistance 是 Casa 大神设计的一个基于 SQLite 封装的数据库框架,在《iOS应用架构谈 本地持久化方案及动态部署》这篇文章中对其设计思想进行了深度的探讨。个人最喜欢的也是这个数据库,本文第二部分 - 数据库设计 - 很大程度上也是参考了这篇博文的思想,因此将在第二部分进行详述。

以上介绍了几个数据库,下表按照提及的两个主要维度对它们进行直观的划分:

SQLite 非SQLite
对象型 CoreData
FMDB衍生类库
WCDB
CTPersistance
Realm
非对象型 FMDB

二. 数据库设计

这里所说的数据库设计更多的是在思考如果要自己封装一个 ORM 型数据库在架构上应该考虑哪些方面。由于本部分比较抽象概括,为便于理解,建议读者先阅读 Casa 大神的这篇文章《iOS应用架构谈 本地持久化方案及动态部署》 后再继续阅读。

我个人觉得数据库框架架构上最看重的是以下两方面(前提这是一款 ORM 型的数据库):

  1. 业务层与数据层能否解耦
  2. 数据库中对象模型在业务层使用过程中能否保证操作安全

1. 解耦

解耦可以提高程序的维护性、便于移植,在数据库的设计中能否解耦业务层与数据层是很重要的一方面。

数据库的设计中离不开三个角色:

  • 数据记录:一条数据记录即数据表中的一行
  • 数据表:数据表代表了我们设计的一个表的结构,里面包含定义的多个字段
  • 数据库管理者:数据库管理者即管理数据库的创建、销毁、多线程及 SQL 语句的执行等等

好的解耦即明晰三个角色的职责,对它们分别进行抽象。数据库管理者是数据库框架核心引擎,属于数据层。数据表是用户定义的数据结构,但其应该是通用的、单一的结构,只含简单的数据操作逻辑,不包含业务逻辑,只是单纯的一张表,其也属于数据层。数据记录是业务层使用的数据结构,属于业务层。

这里盗用 Casa 文章中的一张结构图,这张图很好的展示了业务层至数据层的整体结构划分。分析下 CTPersistance 是如何做到好的解耦的:

  • 将数据库逻辑分为强业务逻辑和弱业务逻辑,弱业务逻辑即数据库的增、删、改、查,由图中 Table 负责,强业务逻辑包含数据缓存、数据组装及相关的业务逻辑(如数据分页、筛选等),由图中 DataCenter 负责,提供业务层友好的API。这里 Table 和 DataCenter 没有耦合,是可以独立存在及复用的。
  • DataCenter 定义相关 Table,并进行数据表操作,获取需要的数据记录 Record。 Record 和 Table 间通过 Virtual Record 协议进行解耦,实现表中字段与数据记录属性间的 ORM 映射。这样 Table 和 Record 实现解耦,表中的数据可以转换为符合协议的多种对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
                 -------------------------------------------
| |
| LogicA LogicB LogicC |-------------------------------> View Layer
| \ / | |
-------\-------/------------------|--------
\ / |
\ / Virtual | Virtual
\ / Record | Record
| |
-----------|----------------------|--------
| | | |
Strong Logics | DataCenterA DataCenterB |
| / \ | |
-----------------|-------/-----\-------------------|-------| Data Logic Layer ---
| / \ | | |
Weak Logics | Table1 Table2 Table | |
| \ / | | |
--------\-----/-------------------|-------- |
\ / | |--> Data Persistance Layer
\ / Query Command | Query Command |
| | |
-----------|----------------------|-------- |
| | | | |
| | | | |
| DatabaseA DatabaseB | Data Operation Layer ---
| |
| Database Pool |
-------------------------------------------

这里延伸下,iOS 中协议是实现解耦的一大利器,如果开发中有解耦的需求,可考虑通过协议的方式进行。

2. 数据操作安全

由于是对象型数据库,数据表和数据记录其字段是一致的,因此很多数据库的封装并没有区分数据表和数据记录这两个角色,将他们抽象为一个数据对象。常用的操作代码如下:

1
2
3
Record *record = [[Record alloc] init];
record.data = @"data";
[record save];

或者

1
2
Record *record = [[Record alloc] init];
NSArray *result = [record fetchList];

可以看到 Record 对象在应用中是可以直接修改数据库中的数据,这样很容易造成业务层对象 Record 误操作而修改数据库中数据,是不安全的。因此在使用这类库时建议加一层数据转换层(类似上图 DataCenter的简化版),将 Record 对象转换为数据库无关的对象,避免误操作。

CTPersistance 由于解耦 Record 和 Table,且 DataCenter 与业务层打交道,而不是通过 Record 对象,因此数据操作是安全的。

三.数据库选取

上文介绍了这么多数据库,那么实际应用中到底该如何选取数据库呢?

如果是个人开发者,功能不复杂,追求开发速度的话 Realm 比较合适,其简单易上手,有专门团队加持,开发文档、工具也相对完善。当然 Realm 自成一派,以后想切换到 SQLite 系数据库就得重新开始。同时 Realm 中数据库对象都必须继承于 Realm Object,iOS 开发中类有单继承的限制,应用过程中也需要考虑到这点。

如果是团队比较强大,有人力来钻研开源代码,可以参考下 WCDB,功能强大,但技术要求比较高,应用成本较高。

我推荐的还是 CTPersistance ,比较欣赏其设计思想,框架解耦且数据库基本功能都具备,也很容易在其上进行功能拓展,同时技术栈难度适合。当然更推荐团队开发借鉴这种思想开发或优化自己的框架,因为数据库涉及的方面很多,不同业务场景下的要求也不尽相同,常听到的一句话就是技术没有最好的,只有最适合的,是吧。