4-2 Core Data–用 SQLite 和 FMDB 替代 Core Data

凭良心讲,我不可能告诉您不去行使 Core
Data。它科学,而且也在变得更好,并且它被很多任何 Cocoa
开发者所领会,当有新娘参预你的团社团照旧须要别人接手你的 app
的时候,那一点很重点。

更主要的是,不值得花时间和生机去写自己的体系去替代它。使用 Core Data
吧。真的。

何以我不行使Core Data

Mike Ash
写到

就个人而言,我不是个狂热粉丝。我意识 (Core Data 的) API
是古板的,并且框架本身对于超越一定数量级的多少的处理是无与伦比缓慢的。

一个实际上的例证:10,000 个条文

想像一个 RSS 阅读器,一个用户可以在一个 feed
上点击右键,并且选拔标记所有为已读。

骨子里贯彻上,我们有一个分包read属性的 Article
实体。把富有条条框框标记为已读,app 必要加载那些 feed 的保有小说(可能通过一对多的涉嫌),然后设置 read 属性为 YES。

一大半时候那样是没问题的。可是设想那一个 feed 有 200
篇小说,为了防止阻塞主线程,你恐怕考虑在后台线程里做那些工作
(尤其是即使那一个 app 是一个 HTC app)。一旦您起来利用 Core Data
十二线程的时候,事情就从头变的不好处理了。

这也许还没那样不佳,至少不值得放任使用 Core Data。

不过,再添加一起。

我用过八个分化的 RSS 同步 API,它们重临已读小说的 uniqueID
数组。其中一个回到近 10,000 个 ID。

你不会打算在主线程中加载 10,000 篇小说,然后设置read为
NO。你几乎也不会想在后台线程里加载 10,000
篇作品,即使很小心地保管内存。那里有太多的工作(如若您往往的这么做,想转手对电池寿命的震慑)。

概念上的话,你真正想要做的是,让数据库将 uniqueID
列表里的每一篇文章的read设置为 YES。

SQLite
可以做到这么些,只用四次调用。就算uniqueID上有索引,那会很快。而且你可以在后台线程执行,那和在主线程执行同样不难。

另一个例子:急忙启动

自身的另一个 app,我想收缩启动时间 — 不只是 app
的起步时间,还有多少显示之前所急需的时刻。

那是个像样 推特(TWTR.US) 的 app
(纵然它不是):它突显信息的年华轴。突显时间轴意味着获取音信,并加载相关用户。它很快,不过在开行的时候,会填充
UI,然后填充数据。

关于 一加app(或者具有应用),我的论争是,启动时间比其余大部开发者想的都要重点。启动时间很慢的
app
是不太可能被启动的,因为人们无形中里会铭记,并且在启动那些应用那件事情上形成一种抵抗心境。减少启动时间可以减少这种阻碍,用户也会更乐于利用你的施用,并且把它推荐给其余人。这是你让你的
app 成功的一部分。

因为我不拔取 Core
Data,我手下有一个简约的,保守的化解方案。我把日子轴(音讯和人选对象)通过NSCoding保存到一个
plist 文件中。启动的时候它读取这一个文件,创设音讯和人员对象,UI
一并发就浮现时间轴。

这明确的回落了延期。

把新闻和人选对象作为NSManagedObject的实例对象,这是无法的。(即使我一度编码并且存储对象的
IDs,不过那表示读取 plist
文件,之后再涉及数据库。那种艺术本身完全幸免了数据库)。

(在更新更快的机器出来后,
我去掉了那个代码。回想过去,我期望自己可以把它留下来。)

本身怎么考虑这么些题材

当考虑是还是不是采纳 Core Data,我着想上边这么些工作:

会有疑心数量的数码吧?

对于一个 RSS 阅读器或者 推特(Twitter)app,答案肯定:是的。有些人关注上百个人。一个人也许订阅了上千个
feed。

即便你的选取不从网络获取数据,用户依然有可能自动抬高数据。假使你用一个支撑
AppleScript 的 Mac,有人会写脚本去加载分外多的数目。要是通过 web API
去丰硕数据也是平等的。

会有一个 Web API 包蕴类似于数据库的结果吧(相比较于类似对象的结果)?

一个 RSS 同步 API 可以回到一个已读作品的 uniqueID
列表。一个笔记的施用的一个联手 API 可能回到已存档的和已删除的笔记的
uniqueID 列表。

sqlite,用户可能因此操作处理多量目标啊?

在尾部,需求考虑和以前一样的题目。当有人删除所有曾经下载的 5,000
个面条食谱,你的食谱 app 性能如何?(在 Motorola 上?)

借使自身主宰运用 Core Data(我早就公布过使用 Core Data
的行使),我会越发注意我怎么着使用它。结果为了赢得好的特性,我发现我把它作为了一个意想不到接口的
SQL 数据库在采取,然后自己就了解了,我应该舍弃 Core Data,而去平素动用
SQLite。

自我何以使用 SQLite

我通过FMDB
Wrapper
来使用
SQLite,FMDB 来自 Flying Meat Software,由 Gus Mueller 开发。

基本操作

在运用 HUAWEI 和 Core Data 之前,我就应用过
SQLite。那里有关于它什么行事的要点:

抱有数据库访问 – 读和写 –
暴发在一个后台线程的接连的行列里。在主线程中触及数据库是从来不被允许的。使用一个一而再队列来担保每一件事是按梯次暴发的。

自己大方利用 blocks 使得异步编程简单些。

模型对象只设有在主线程(但有两个根本的两样),改变会接触一个后台保存。

模型对象列出来它们在数据库中存储的性质。这说不定在代码里如故在 plist
文件里。

多少模型对象是唯一的,有些不是。取决于 app
的必要(一大半气象是唯一的)。

对关系型数据,我竭尽幸免创立查询表。

部分对象类型在启动的时候就全盘读入内存,另一部分对象类型我也许创制和掩护的唯有它们
uniqueID 的一个
NSMutableSet,所以自己可以在不去碰数据库的事态下就知道哪些存在、什么不存在。

Web API 的调用发生在后台线程,它们选拔“分离“的模型对象。

自我会采用本人目前的
app
的代码来描述。

数据库更新

在自我目前的 app 中,有一个单纯的数据库控制器
-VSDatabaseController,它通过 FMDB 来与 SQLite 对话。

FMDB 区分更新和询问。更新数据库,app 调用:

VSDatabaseUpdateBlock很简单:

runDatabaseBlockInTransaction也很简短:

(注意自身用的亲善的接连 dispatch 队列。Gus提议看一下FMDatabaseQueue,那也是一个接连调度队列。因为它比 FMDB
剩下的其余东西都要新,所以我自己还尚无去看过。)

beginTransaction和endTransaction的调用是可嵌套的(在我的数据库控制器里)。在恰当的时候他们会调用-[FMDatabase
beginTransaction]和-[FMDatabase commit]。(使用 transactions 是让
SQLite 变快的一大紧要。)提醒:我在-[NSThread
threadDictionary]中储存当前的 transaction
的计数。那对于针对每个线程的数据来说是很便宜的,我也大约从不要它做其余的作业。

此刻有个调用更新数据库的粗略例子:

那注明了重重作业。首先, SQL
并不吓人。即便你从未见过它,你也精晓这行代码做了什么。

像VSDatabaseController的所有其余公共接口一样,emptyTagsLookupTableForNote也理应在主线程中被调用。模型对象只能够在主线程中被引用,所以在
block 中应用uniqueID,而不是 VSNote 对象。

专注在那种气象下,我更新了一个查询表。Notes 和 tags
有一个多对多关系,一种表现方法是用一个数据库表映射 note uniqueIDs 和 tag
uniqueIDs。那个表不会很难保险,不过倘若可能,我尽量防止使用它们。

留神在立异字符串中的?。-[FMDatabase
executeUpdate:]是一个可变参数函数。SQLite 辅助接纳占位符 – ? 字符 –
所以你不需求把实际的值放入字符串中去。那是一个安全上的勘察:它可以守护程序防止SQL 注入。它也得以扶持您收缩必须 escape 值那样的不须求的劳动。

最终,注意在 tagsNotesLookup 表中,有一个 noteUniquelID 的目录(索引是
SQLite 性能的又一个非同儿戏)。那行代码在每一趟启动时都调用:

数据库获取

要博得对象,app 调用:

那两行代码做了绝大部分行事:

用 FMDB 查找数据库重临一个FMResultSet. 通过 resultSet
你可以逐句循环,创设模型对象。

本身提议写通用的代码去将数据库中的行转换为目标。一种自我曾经应用的办法是在
app 中用一个 plist
文件,将列的名字映射到模型对象的属性上去。它也暗含类型,所以你明白是调用-[FMResultSet
dateForColumn:]还是-[FMResultSet stringForColumn:]或是其他措施。

在自我的新颖 app
里本身做的作业更简明。数据库行刚好对应模型对象属性的名字。除了那个名字以
“Date”
结尾的性质以外,所有属性都是字符串。简单,可是你可以见见所急需肯定清晰的对应关系。

唯一目标

创设模型对象的操作和从数据库获取数据操作在平等的后台线程进行。一但获得到,app
会把它们转到主线程。

平凡自己会采取唯一目的。数据库里的一样行,始终对应着同等的一个对象。

为了做到唯一,我利用 NSMapTable 创建了一个对象缓存,在 init
函数里:_objectCache = [NSMapTable
weakToWeakObjectsMapTable]。我来解释一下:

比如,当你进行一个数据库获取操作并且把目的传递给一个视图控制器时,你希望在那几个视图控制器选取完这几个目的后,或者在一个不平等的视图控制器被彰显后,那一个目标足以没有。

若是你的目的缓存是一个NSMutableDictionary,那您将索要做一些额外的行事来清空缓存中的对象。有限接济它只援引了这么些其余地方有引用的目的是一件越发让人蛋疼的作业。而利用同盟弱引用的NSMapTable,那些问题就被机关处理掉了。

据此:我们在主线程中让对象唯一。要是一个目标已经在目的缓存中留存,我们就用越发存在的靶子。(因为主线程中目的可能有改变,由此在争持时我们运用主线程的目的。)假使目的缓存中没有,它会被抬高。

有限支撑对象在内存中

有广大次,把全路对象类型保留在内存中是有道理的。我最新的 app 有一个
VSTag 对象。纵然可能有那些篇笔记,但 tags
的多寡很小,基本少于十个。一个 tag 唯有 6 个特性:多少个 BOOL,七个很小的
NSstring,还有一个 NSDate。

开行的时候,app 获取具有 tags 并且把它们保存在多少个字典里,其中一个的键是
tag 的 uniqueID,另一个的键是 tag 名字的小写。

那简化了重重事,比如 tag
自动补全系统,就足以完全在内存中操作,而不须要从数据库获取了。

不过很频仍,把富有数据保存在内存中是不实际的。比如大家不会在内存中保存所有笔记。

不过也有那些次,把具有目的保存在内存中是不可行的。当不可能在内存中保存一个对象类型时,你或许会希望在内存中保留所有
uniqueID,你可以展开那样一个拿走操作:

resultSet 只包蕴了 uniqueIDs, 你能够储存到一个 NSMutableSet 里。

自家发现有时这些对 web APIs 很有用。想象一个 API
再次来到从某个确定的小时之后所创办笔记的 uniqueIDs
列表。即使我本地曾经有了一个包括所有笔记 uniqueIDs 的
NSMutableSet,我得以 (通过-[NSMutableSet minusSet])
火速检查是还是不是有遗漏的笔记,然后去调用另一个 API
下载那个漏掉的笔记。那几个统统不必要接触数据库。

然而,像那样的事情应该小心处理。app
可以提供丰富的内存吗?它的确简化编程并且增长性能了呢?

使用 SQLite 和 FMDB 来顶替 Core
Data,会给您带来大气的八面后珑和利用更智慧的法子来化解问题的空中。记住有的时候聪明是好的,也部分时候聪明是一个大错误。

Web APIs

我的 API
调用都跑在后台进度里(常常是用一个NSOperationQueue,那样自己可以打消操作)。模型对象只在主线程,然后将模型对象传递给我的
API 调用。

切实这么做:一个数据库对象有一个detachedCopy方法,可以复制数据库对象。这几个复制的目标不会被自己用来做唯一化的靶子缓存所引述。唯一引用这些目的的地点是
API 调用,当 API 调用截至时,那个复制的靶子也就没有了。

那是一个好的系统,因为它表示自己得以在 API
调用里应用模型对象。方法看起来像那样:

VSNoteAPICall 从分离出来的VSNote中获取值,并且创办 HTTP 请求,而不是将
note 包装成一个字典或其余表现形式。

处理 Web API 的重返值

本身对 web 的重返值做了一部分好像的处理。我会对回到的 JSON 或者 XML
创造一个模子对象,这些模型对象也是分其余。它没有存储在唯一化模型缓存里。

那里有些事情是不确定的。有时我们须求用相当模型对象在在内存缓存以及数据库四个地点做当地修改。

数据库一般是不难的部分。比如:我的 app
已经有一个格局来保存笔记对象。它使用 SQL 的insert or
replace命令。我只需用从 web API
再次回到值所生成的笔记对象来进展调用,数据库就会更新。

唯独可能同样的对象在内存中还有一个本子,幸运的是我们很不难找到它:

若果 cachedNote 存在,我会让它从 downloadedNote中
获取值(这一部分可以共享detachedCopy方法的代码。),而不是直接互换它(这样也许违反唯一性)。

若是 cachedNote 更新了,观看者会通过 KVO
察觉到变化,或者我会发送一个NSNotification,或者双方都做。

Web API 调用也会回到一些任何值。我关系过 RSS
阅读器可能得到一个已读条目标大列表。那种场地下,我拔取经过丰富列表制造一个NSSet,在内存的缓存中更新每一个缓存文章的read属性,然后调用-[FMDatabase
executeUpdate:]。

完了这几个工作的要害是NSMapTable的探寻是很快的。假使你找的靶子在一个
NSArray 里,大家就得重新考虑考虑了。

数据库迁移

当健康办事的时候,Core
Data 的数据库迁移功能仍旧蛮酷的。

而是不可防止的,它在代码和数据库中投入了一层。倘诺你更直接一点,去行使
SQLite,那么更新数据库也就变得越直接。

你可以高枕无忧简单的做到这一点。

诸如加一个表:

或丰裕一个索引

或添加一列:

app 应该用类似下边那样的代码来第一对数据库举办安装。未来的改动就是丰盛对
executeUpdate 的调用 —
我让她们按顺序执行。因为我的数据库是我设计的,所以那不会有怎么样问题(我未曾蒙受性能问题,它很快)。

自然大的更动需求更加多代码。借使您的多少通过 web
获取,有时你可以从一个新数据库模型先河,重新下载你须求的数码。

性能技巧

SQLite 能够卓殊卓殊快,可是也足以丰硕慢。完全在于你怎么使用它。

事务

把立异包装在作业里。在更新前调用-[FMDatabase
beginTransaction],更新后调用-[FMDatabase commit]。

假如您不得不反规范化( Denormalize)

反规范化令人很不爽。那几个艺术是,为了加快检索而添加冗余数据,可是它意味着你须求维护冗余数据。

自家连续努力幸免它,直到那样能有非同儿戏的性质差别。然后我会尽可能少得如此做。

使用索引

自己的 app 中 tags 表的创制语句像这样:

uniqueID 列是活动索引的,因为它定义为 unique。可是只要本身想用 name
来查询表,我说不定会在name上创建一个目录,像那样:

你可以两遍性在多列上创设索引,像这么:

而是注意太多索引会下落你的插入速度。你只须要丰富数量并且是未可厚非的那多少个。

利用命令行应用

当自家的 app 在模拟器里运行时,我会用NSLog输出数据库的路子。我得以由此sqlite3 的命令行来打开数据库。(通过 man sqlite3
命令来打听那几个动用的越多音信)。

开拓数据库的一声令下:sqlite3 path/to/database。

开拓未来,你可以输入.schema来查阅 schema。

您可以创新和询问,那是在你的 app 使用 SQL
以前就将它们正确地准备妥当的很好的办法。

那之中最酷的一有些是,SQLite Explain Query Plan
命令
,你会愿意确保您的话语执行的尽量快。

真实的例子

自身的 app
呈现所有没有归档笔记的竹签列表。每当笔记或者标签有浮动,这一个查询就会重复履行四回,所以它需求很快。

自己可以用SQL
join
来询问,不过这会很慢(join
都很慢)。

就此我扬弃 sqlite3 并开首尝试其他方法。我又检查了两遍我的
schema,意识到自身可以反规范化。一个笔记的存档状态可以储存在 notes
表里,它也得以储存在 tagsNotesLookup 表。

下一场我得以实施一个询问:

自身一度有了一个在 tagUniqueID 上的目录。所以我用 explain query plan
来报告自己当自己执行这一个查询的时候会发出什么。

它用了一个目录,那很不错,可是 SCAN TABLE 听起来不太好,最好是一个
SEARCH TABLE
加上覆盖索引的方式。

自己在 tagUniqueID 和 archive 上建了目录:

再次实施 explain query plan:

现行好多了。

越多属性提醒

FMDB 的某处加了缓存 statements
的能力,所以当成立或打开一个数据库的时候,我连连调用[self.database
setShouldCacheStatements:YES]。那代表对种种调用你不须要再行编译每个
statement。

本身从来不曾找到关于利用vacuum的好的率领。如果数据库没有期限压缩,它会变得尤其慢。我的
app 会周周跑一遍 vacuum。(在 NSUserDefaults 里储存上次 vacuum
的时刻,然后在开首的时候检查是还是不是过了一周)。

使用auto_vacuum可能会更好,可以参考pragma statements supported by
SQLite
列表。

其他酷的东西

格斯(Gus) Mueller 让自身讲讲自定义 SQLite
方法的始末。我并从未真的拔取过那几个东西,不过既然他提议了,我可以放心的说我能找到它的用途。因为它很酷。

在 Gus 的这个 gist
,有一个查询是那般的:

SQLite 完全不知道 UTTypes
的业务。不过你可以由此代码块来添加主题措施,感兴趣的话,可以看看-[FMDatabase
makeFunctionNamed:maximumArguments:withBlock:]方法。

你可以举行一个大的询问来替代,然后评估每个对象 –
可是那需求更加多办事。最好在 SQL
级就过滤,而不是在将表格行转为对象将来再做那件业务。

最后

您真的理所应当选择 Core Data,我不是在开玩笑。

本人用 SQLite 和 FMDB
一段时间了,我对多得到的功利感到很提神,也取得非同寻常的性能。

然则切记设备在相连变快。也请记住,其余看你代码的人盼望看到 Core
Data,那是他俩曾经了然的 – 他们不打算看您的数据库代码怎样工作。

之所以请把那整篇小说看做一个神经病的吵嚷,关于她为友好建立了充满细节又疯狂的社会风气

  • 并把温馨锁在了里面。

有点悲哀的撼动,并且请享受这一个话题下那多少个超赞的 Core Data 的篇章吧。

而对本身来说,接下去在探究完 格斯 提议的自定义 SQLite 方法特性后,我会研讨SQLite
全文检索扩充
总有越多的始末要求持续去学习。


翻译小编:answer-huang

网站地图xml地图