摘要:作为微信的终端数据库,从开源至今,共迭代了个版本。微信也转向开发了吗相信这会是大家非常关心的问题。不仅微信,国内外大部分都还没有完全转向,但显然这是个趋势。另一方面,没有微信的上线机制的保护和庞大的用户量的验证,我们需要确保的稳定性。
WCDB 作为微信的终端数据库,从 2017.6 开源至今,共迭代了 5 个版本。我们一直关注开发者们的需求,并不断优化性能,新增如全文搜索等常用的功能。而这其中,呼声最高的莫过于 对 Swift 的支持。
WCDB ObjC 版本的实现中,由于引入了 C++ 代码,并不能直接 bridge 到 Swift。因此,我们从 9 月份开始就着手使用原生的 Swift,重写 WCDB。并于 10.10 和 11.8 分别在开发者群内发布了 alpha 和 beta 版进行测试。
今天,终于可以正式发布 WCDB Swift 的第一个正式版本了。
WCDB Swift 约有 1.5w 行代码,使用 Pure Swift 编写,几乎不包含 Cocoa 的代码。且与 ObjC 版保持完全一致的功能。
模型绑定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,进行预期之外的恶意操作。深入 SQLite 源码的性能优化
WCDB 基于 SQLite 开发,我们在之前的文章介绍过其对 SQLite 源码进行的性能优化,以适配移动终端的场景。同样地,这部分优化 Swift 版本也能享受到。
线程安全且并发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 提供简单易用的全文搜索接口,并包含适配多种语言的分词器,使得数据搜索更精准。
损坏修复内建的修复工具可以在系统错误、磁盘故障等情况下,尽最大限度地将损坏的数据找回并导出。
Pure Swift模型绑定对语言的依赖性很大。由于 ObjC 其强大的消息转发机制,使得 WCDB 实现起来并没有太大的问题。然而,动态性却恰恰是 Swift 一直为人诟病的地方。
最省事的解决方案就是,直接引入 Cocoa,所有的问题都将不再是问题。然而,这并不是我们所期望的。
理性分析可以得出,一方面,全面的动态化会拖累 Swift 的性能,另一方面,这也会使得 Swift 的原生类型难以享受到模型绑定。
但我们的理由可能更感性一些 --- 情怀。称之为强迫症也好,代码洁癖也罢,Swift with Cocoa 总让人心里有那么一丝别扭。因此,我们决定寻找 Swift 原生的解决方案。
WCDB 的模型绑定对语言有两点依赖:
Accessor。ObjC 版本使用 selector 的 IMP 指针,使得 WCDB 可以获取变量的值,并插入到数据库中,或从数据库中获取数据写入到变量。
数据库字段的映射。ObjC 版本使用宏定义,使得 WCDB 可以通过 className.propertyName 的方式进行语言集成查询的操作。
KeyPath我们最初盯上的是 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。
Codable在 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)微信也转向 Swift 开发了吗?
相信这会是大家非常关心的问题。然而,很遗憾,目前还没有。不仅微信,国内外大部分 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 成为开发者的首选的那一天。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/17674.html
摘要:本文即以简单的回归拟合为例,从最基础的库安装数据导入数据预处理到模型训练模型预测介绍了如何使用进行简单的机器学习任务。 前端每周清单第 18 期:Firefox、Chrome、React、Angular发布新版本;提升RN应用性能的方法 为InfoQ中文站特供稿件,首发地址为这里;如需转载,请与InfoQ中文站联系。从属于笔者的 Web 前端入门与工程实践的前端每周清单系列系列;部分...
阅读 2528·2021-07-26 23:38
阅读 3430·2019-08-30 13:10
阅读 2314·2019-08-29 18:33
阅读 2319·2019-08-29 16:12
阅读 986·2019-08-29 10:59
阅读 1796·2019-08-26 17:40
阅读 764·2019-08-26 11:59
阅读 810·2019-08-26 11:41