WCDB 做爲微信的終端數據庫,從 2017.6 開源至今,共迭代了 5 個版本。咱們一直關注開發者們的需求,並不斷優化性能,新增如全文搜索等經常使用的功能。而這其中,呼聲最高的莫過於 對 Swift 的支持。git
WCDB ObjC 版本的實現中,因爲引入了 C++ 代碼,並不能直接 bridge 到 Swift。所以,咱們從 9 月份開始就着手使用原生的 Swift,重寫 WCDB。並於 10.10 和 11.8 分別在開發者羣內發佈了 alpha 和 beta 版進行測試。github
今天,終於能夠正式發佈 WCDB Swift 的第一個正式版本了。數據庫
WCDB Swift 約有 1.5w 行代碼,使用 Pure Swift 編寫,幾乎不包含 Cocoa 的代碼。且與 ObjC 版保持徹底一致的功能。swift
WCDB Swift 的模型綁定,基於 Swift 4.0 的 Codable
協議實現。經過創建 Swift 類型與數據庫表之間的映射關係,使得開發者能夠經過類對象直接操做數據庫。安全
//Sample.swift
class Sample: TableCodable {
var identifier: Int? = nil
var description: String? = nil
var unused: Int? = nil
enum CodingKeys: String, CodingTableKey {
typealias Root = Sample
static let objectRelationalMapping =
TableBinding(CodingKeys.self)
case identifier
case description
}
}
//main.swift
let tableName = "sampleTable"
try database.create(table: tableName,of: Sample.self)
let object = Sample()
object.identifier = 1
object.description = "sample_insert"
try database.insert(objects: object, intoTable: tableName)
複製代碼
語言集成查詢深度結合了 Swift 和 SQL 的語法,使得純字符串的 SQL 能夠以代碼的形式表達出來。結合代碼提示及糾錯,極大地提升開發效率。性能優化
同時,因爲 Swift 的語法 比 Objective-C 更加簡潔,並有更強大的範型和類型推導,使得 WCDB 接口不只更易編寫,並且更易讀易維護。微信
let objects: [Sample] = try database.getObjects(fromTable: tableName,
where: Sample.Properties.identifier > 0 && Sample.Properties.description.isNotNull())
複製代碼
相似 Sample.Properties.identifier > 0
的語法,其返回值並不爲 Bool
,而是語言集成查詢的 Expression
對象,WCDB 會根據這個語句,去進行 SQL 的查詢。同時,經過類型的定義,Swift 便可推導出 WCDB 查詢的結果爲 Sample
類。併發
語言集成查詢同時內建了反注入機制,能夠避免第三方從輸入框注入 SQL,進行預期以外的惡意操做。app
WCDB 基於 SQLite 開發,咱們在以前的文章介紹過其對 SQLite 源碼進行的性能優化,以適配移動終端的場景。一樣地,這部分優化 Swift 版本也能享受到。ide
WCDB Swift 不只能夠安全地在任意線程進行數據庫操做,且其內部會智能地根據操做類型調配資源,使其可以併發執行,進一步提高效率。
基於 SQLCipher 的加密機制,能夠爲客戶端數據安全提供必定程度的保障。
數據庫模型與類定義綁定,使得字段的增長、刪除、修改都與類變量的定義保持一致,不須要開發者額外地管理字段的版本。
class Sample: TableCodable {
var identifier: Int? = nil
var description: String? = nil
var newColumn: Date? = nil
var unused: Int? = nil
enum CodingKeys: String, CodingTableKey {
typealias Root = Sample
static let objectRelationalMapping =
TableBinding(CodingKeys.self)
case identifier
case description
case newColumn
}
}
let tableName = "sampleTable"
try database.create(table: tableName,of: Sample.self)
複製代碼
模型綁定中新增了 newColumn
字段,該字段也會被自動建立到數據庫表中,開發者不須要手動管理。
WCDB Swift 提供簡單易用的全文搜索接口,幷包含適配多種語言的分詞器,使得數據搜索更精準。
內建的修復工具能夠在系統錯誤、磁盤故障等狀況下,盡最大限度地將損壞的數據找回並導出。
模型綁定對語言的依賴性很大。因爲 ObjC 其強大的消息轉發機制,使得 WCDB 實現起來並無太大的問題。然而,動態性卻偏偏是 Swift 一直爲人詬病的地方。
最省事的解決方案就是,直接引入 Cocoa,全部的問題都將再也不是問題。然而,這並非咱們所指望的。
理性分析能夠得出,一方面,全面的動態化會拖累 Swift 的性能,另外一方面,這也會使得 Swift 的原生類型難以享受到模型綁定。
但咱們的理由可能更感性一些 --- 情懷。稱之爲強迫症也好,代碼潔癖也罷,Swift with Cocoa 總讓人內心有那麼一絲彆扭。所以,咱們決定尋找 Swift 原生的解決方案。
WCDB 的模型綁定對語言有兩點依賴:
selector
的 IMP
指針,使得 WCDB 能夠獲取變量的值,並插入到數據庫中,或從數據庫中獲取數據寫入到變量。className.propertyName
的方式進行語言集成查詢的操做。咱們最初盯上的是 Swift 的 KeyPath
的機制,它經過 \ 的語法,能夠直接對變量進行讀寫操做,且語法上也與 className.propertyName
相似。
let sample=Sample()
sample[keyPath: \Sample.identifier] = 1
print(sample[keyPath: \Sample.identifier]) // 輸出 1
複製代碼
一個難題是,KeyPath
在不引入 Cocoa 的狀況下,是並不提供 property 的名稱,這就沒法經過 KeyPath
直接映射數據庫的字段。
Swift 也有一個相關的 SR 在討論這個問題。
顯然,咱們不可能等待這個特性實現了再去作 WCDB Swift。所以咱們嘗試使用「不常規」的方法,獲取到 KeyPath
對應的 property 名稱。
Mirror
是 Swift 裏的反射類型,它能夠遍歷每一個變量,獲取其名稱和值,但不能對變量寫入數據。所以咱們能夠經過 KeyPath
對變量設一個獨一無二的特徵值,而後再經過 Mirror
遍歷變量,導出與特徵值相同的 property 名稱。
sample[keyPath: \Sample.identifier] = 0x539D7C2 // 不易衝突的特徵值
let mirror = Mirror(reflecting: sample)
for child in mirror.children {
if child.value == 0x539D7C2 {
print(child.label)
break
}
}
複製代碼
這個「不常規」的用法在大部分狀況下可以生效,但對於 class
和 struct
相互嵌套的變量,容易由於內存混亂致使 crash。
在 KeyPath
的方案不夠完善的狀況下,咱們轉投了 Codable
協議。它是 Swift 4.0 新增的特性,本質是編譯前根據定義生成代碼,以完成序列化和反序列化的任務。
// Swift 官方文檔中的 Codable 示例
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
var vantagePoints: [Coordinate]
enum CodingKeys: String, CodingKey {
case name = "title"
case foundingYear = "founding_date"
case location
case vantagePoints
}
}
let data = try JSONEncoder().encode(landmark)
複製代碼
對應到 WCDB,將數據庫的字段讀寫到變量中,其本質就是一個序列化和反序列化的過程,而 CodingKeys
也可能能夠用於語言集成查詢中的字段映射。
然而,因爲這個特性還很新,尚未太多文檔對其進行深刻介紹,尤爲是自定義 Encoder
和 Decoder
這部分。
所幸的是,Swift 自己就是開源的。所以,咱們參考 swift-corelibs-foundation 中的 JSONEncoder
和 JSONDecoder
,實現了 TableEncoder
和 TableDecoder
,並經過 CodingKeys
的定義,映射數據庫中的字段。
最終維護了咱們對 Pure Swift 的堅持。
class Sample: TableCodable {
var identifier: Int? = nil
var description: String? = nil
var offset: Int = 0
var debugDescription: String? = nil
enum CodingKeys: String, CodingTableKey {
typealias Root = Sample
static let objectRelationalMapping =
TableBinding(CodingKeys.self)
case identifier = "id"
case description
case offset = "db_offset"
}
}
let sample: Sample = try database.getObject(fromTable: tableName,
where: Sample.Properties.identifier==1)
複製代碼
相信這會是你們很是關心的問題。然而,很遺憾,目前尚未。不只微信,國內外大部分 app 都尚未徹底轉向 Swift,但顯然這是個趨勢。
Google 在 11 月 fork 了 Swift。
你們舉棋不定的緣由都大同小異:ABI 不穩定,須要將二進制打包進去,增大 app 體積;某些方面性能還不夠好,並且如今多數是與 ObjC 混編,將進一步拉低性能 等等。
而這其中一個很重要的緣由就是,Swift 的基礎設施還不完善,還難以支撐其大型 app 的開發。而 WCDB Swift 就是這類基礎設施之一。
所以,先有 WCDB Swift,將來纔有用 Swift 編寫微信的可能,這邏輯沒毛病。
另外一方面,沒有微信的上線機制的保護和龐大的用戶量的驗證,咱們須要確保 WCDB Swift 的穩定性。所以,在 WCDB Swift 的初版本,咱們就提供了相對完善的測試用例,用例的代碼覆蓋率爲 91.34%,可以觸達絕大部分使用場景。
更多 WCDB Swift 的教程文檔、代碼樣例,包括源碼,都直接到 Github 的 Tencent/wcdb 瞭解。
咱們一塊兒期待 Swift 成爲開發者的首選的那一天。