前言:

iOS 中常用的数据库有 CoreDataSQLiteFMDB 等等,其中 CoreData 和 Xcode 深度结合,易费用较差; SQLite 自身便是C言语,运用需求了解C言语接口FMDB 是对 SQLite 的一层封装,很多胶水代码,依然自己需求写 SQL 语句,而 WCDB 是微信团队开发的一个易用、高效、完整的移动数据库结构,它根据 SQLiteSQLCipher 开发,支撑加密、损坏检测、数据备份、和数据修正,在微信中运用广泛,且支撑在 C++SwiftObjc 三种言语环境中运用。

微信移动端数据库组件 WCDB

WCDB 根底调用

最根底的调用进程大致分为三个过程:

  1. 模型绑定
  2. 创立数据库与表
  3. 操作数据

一、模型绑定

假定存在一个Person类:

class Person {
    var identifier: Int = 0
    var name: String? = nil
    var age: Int = 0
}

要把该类的数据存储进数据库,能够采用 WCDB 的文件模板:

1. 文件与代码模版

假如没有获取 WCDBGithub 仓库,能够终端执行命令:

curl https://raw.githubusercontent.com/Tencent/wcdb/master
/tools/templates/install.sh -s | sh

翻开 cmd + n 拉到最下面:

微信移动端数据库组件 WCDB

选swift创立,因为模板是旧代码,需求把import WCDB改成import WCDBSwift

2. WCDB Swift 的模型绑定分为五个部分:

  1. 字段映射
  2. 字段束缚
  3. 索引
  4. 表束缚
  5. 虚拟表映射

1)字段映射

  • 在类内界说 CodingKeys 的枚举类,遵循 StringCodingTableKey
  • 把想要存储的变量写到枚举 CodingKeys 里面的 case 后边,表明绑定到了数据库表中的字段;
  • 把需求在数据库重命名的字段,进行别号映射, case identifier = "id" 表明把 identifier 在数据库表中重命名为id
  • 假如运用的字段与 SQLite 字段关键字相同,也需求做别号映射。

2)字段束缚

字段束缚,它用于针对单个字段的束缚,例如主键束缚、非空束缚、唯一束缚,默许值等等,能够根据自己的需求选择完成或许不完成。方法是 columnConstraintBindings:Github 上和 TableCodable 模板默许生成的是以前的代码,新代码如下:

static var columnConstraintBindings: [CodingKeys:
ColumnConstraintBinding]? {
    return [
        identifier: ColumnConstraintBinding(isPrimary: true),
        name: ColumnConstraintBinding(isNotNull: true, defaultTo:"空"),
        age: ColumnConstraintBinding(isNotNull: true, defaultTo: 0)
    ]
}

3)自增属性

isAutoIncrement 表明是否自增,束缚界说 isPrimarytrue,支撑自增,可是依然能够支撑非自增方法刺进。

当需求自增刺进,需求设置 isAutoIncrementtrue,数据库会将主键最大值 + 1 作为新的最大主键值。

索引,表束缚,虚拟表映射相对复杂,一般表用不到,这里就不写。

4)swift6 错误正告

因为 Github上WCDB 的是 swift4.0 的代码,现在运用有些需求修改,会提示:

微信移动端数据库组件 WCDB

class Person 前面添加 final 消除正告。

最终模型绑定代码

final class Person: TableCodable {
    var identifier: Int = 0
    var name: String? = nil
    var age: Int = 0
    enum CodingKeys: String, CodingTableKey {
        typealias Root = Person
        case identifier = "id"
        case name
        case age
        static let objectRelationalMapping = 
        TableBinding(CodingKeys.self)
        static var columnConstraintBindings: 
        [CodingKeys: ColumnConstraintBinding]? {
            return [
                identifier: ColumnConstraintBinding
                (isPrimary: true),
                name: ColumnConstraintBinding
                (isNotNull: true, defaultTo: "空"),
                age: ColumnConstraintBinding
                (isNotNull: true, defaultTo: 0)
            ]
        }
    }
    var isAutoIncrement: Bool = true
}

二、创立数据库与表

1. 创立数据库

var database = Database(withPath: NSSearchPathForDirectoriesInDomains(
            .documentDirectory,
            .userDomainMask,
            true).last!+”/Person/person.db")

2. 创立表

一行代码就创立表:

try database?.create(table: "personTable", of: Person.self)

因为 WCDB 推荐用表操作数据,所以能够获取表对象:

var personTable = try database.getTable(named: "personTable")

三、操作数据

1. 刺进操作

向表中刺进一条数据,id 前面已经界说了自增:

 try database?.insert(objects: p, intoTable: "personTable")

WCDB 推荐操作表,因为操作的对象更明确,更简练,后边代码都是表操作:

 try personTable?.insert(objects: p)

2. 删去操作

示例代码,删去 id2 的数据:

try personTable?.delete(where: Person.Properties.identifier == 2)

3. 更新操作

示例代码,更新 id2 的数据的 name 字段:

try personTable?.update(on: Person.Properties.name, with: p,
where:  Person.Properties.identifier == 2 )

4. 查找操作

示例代码,查找年纪大于 25 的数据:

 let persons: [Person]! = try personTable?.getObjects(where:
 Person.Properties.age > 25)

主要功能代码都是一行代码搞定,而且让增修改查的语法共同,运用十分便利。

四、数据库、表

1. 翻开数据库

因为 WCDB 是采用推迟初始化,运用时候才会创立而且初始化,所以不需求手动调用 open,但能够运用 database.canOpen 测试数据是否能够正常翻开,另外 database.isOpened 需求创立表之后才会 true

2. 封闭数据库

WCDB 一般情况下不需求开发者手动调用封闭,假如操控器被收回,数据库会主动封闭,而且主动收回内存。当然,也能够手动调用,一般都是根据文件操作,比方移动文件影响到了数据库的数据,才需求手动封闭,接口是:

try database.close(onClosed: {
    try database.moveFiles(toDirectory: otherDirectory)
})

3. 表

通过 getTable 接口获取数据库中的一个表:

let table = database.getTable(named: "sampleTable", of: Sample.self)

WCDBTable 具有了 database 的所有增修改查接口,而且更简练,以表为单位来办理数据读写逻辑更合理便利,所以尽量运用 Table 来进行数据读写操作,上面代码已经演示。

五、业务

业务一般用来提高功能和保证操作的原子性,通过 transaction 操控业务。

1. 功能

假定给数据库刺进 100 万条数据,看消耗时间和运用业务的优化情况,先准备 100 万条数据:

print("startTime ------------------" + startTime)
var persons:[Person] = [];
for i in 1...1000000{
    let p = Person()
    p.age = Int(arc4random_uniform(20)) + 10;
    p.name = String(format: "张%d", i)
    persons.append(p)
}

1)普通刺进操作

// 单独刺进,功率很差
for p in persons {
    do{
        try personTable!.insert(objects: p)
    }catch let error{
        debugPrint("刺进数据失利 \(error.localizedDescription)")
    }
}
let endTime = Self.getCurrentTime(timeFormat: TimeFormat.HHMMSS)
print("endTime ------------------" + endTime)

数据库表中如下:

微信移动端数据库组件 WCDB

运转打印:

startTime ------------------ 12:05:43
endTime   ------------------ 12:06:23

普通操作,刺进 100 万条数据,耗时差不多 41 秒。

2)业务刺进操作

// 业务刺进,功能较好
do{
    try database.run(transaction: {
        for object in persons {
            try personTable?.insert(objects: object)
        }
    })}catch let error{
        debugPrint("刺进数据失利 \(error.localizedDescription)")
    }
let endTime = Self.getCurrentTime(timeFormat: TimeFormat.HHMMSS)
print("endTime ------------------" + endTime)

运转打印:


startTime ------------------ 12:14:42
endTime   ------------------ 12:14:55

业务操作,刺进 100 万条数据,耗时差不多 13 秒,功能有显着提高。

3)内置业务刺进操作

insert(objects:) 接口内置了业务,并对批量数据做了针对性的优化,功能更好

do{
    try personTable?.insert(objects: persons)
}catch let error{
    debugPrint("刺进数据失利 \(error.localizedDescription)")
}
let endTime = Self.getCurrentTime(timeFormat: TimeFormat.HHMMSS)
print("endTime ------------------" + endTime)

运转打印:

startTime ------------------ 12:23:05
endTime   ------------------ 12:23:08

能够看出,内置业务接口的优化十分显着,刺进 100 万条数据只运用了 3 秒。

2. 原子性

在多线程下,删去数据,一起刺进一条数据,操作在瞬间,很难确实哪个先执行:

1)非业务操作

DispatchQueue(label: "other thread").async {
    do{
        try self.personTable?.delete()
    }catch let error{
        debugPrint("业务操作失利 \(error.localizedDescription)")
    }
}
do{
    let p = Person()
    p.age = Int(arc4random_uniform(20)) + 10;
    p.name = "马可bro"
    try personTable?.insert(objects: p)
    let objects = try personTable?.getObjects()
    print(objects?.count ?? "出错") // 或许输出 0 或 1
}catch let error{
    debugPrint("业务操作失利 \(error.localizedDescription)")
}

成果:或许输出 012

2)业务操作

DispatchQueue(label: "other thread").async {
    do{
        try self.personTable?.delete()
    }catch let error{
        debugPrint("业务操作失利 \(error.localizedDescription)"
    }
}
do {
    try database.run(transaction: {
        let p = Person()
        p.age = Int(arc4random_uniform(20)) + 10;
        p.name = "大小姐"
        try personTable?.insert(objects: p)
        let objects = try personTable?.getObjects()
        print(objects?.count ?? "出错") // 输出 1
    })
}catch let error{
    debugPrint("业务操作失利 \(error.localizedDescription)")
}

成果:只会输出 1

参阅

  • Tencent/wcdb
  • 微信移动端数据库组件WCDB系列