WeChat mobile database component WCDB

Foreword:

Commonly used databases in iOS include CoreData, SQLiteand , FMDBetc., among which CoreDatais deeply integrated with Xcode, and the ease of use is relatively poor; SQLiteit is a C language itself, and you need to understand the C language interface to use it; FMDBit is SQLitea layer of encapsulation of , and many glue codes still need to be written by yourself SQLstatement, but WCDBis an easy-to-use, efficient, and complete mobile database framework developed by the WeChat team. It is based on SQLiteand SQLCipherdeveloped to support encryption, damage detection, data backup, and data repair. It is widely used in WeChat and supports in C++, Swift, and Objcthree used in a language environment.

CodeStructure.png

WCDB basic calls

The most basic calling process is roughly divided into three steps:

  1. model binding
  2. Create database and tables
  3. operating data

1. Model binding

Suppose there exists a Personclass:

class Person {
    var identifier: Int = 0
    var name: String? = nil
    var age: Int = 0
}
复制代码

To store data of this type into the database, you can use WCDBthe file template of :

1. Files and code templates

If you have not obtained WCDBthe Githubwarehouse, you can execute the command in the terminal:

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

Open cmd + nand pull to the bottom:

Pasted Graphic.png

Choose swift to create, since the template is old code, you need to import WCDBchange it toimport WCDBSwift

2. The model binding of WCDB Swift is divided into five parts:

  1. field mapping
  2. field constraints
  3. index
  4. table constraints
  5. virtual table mapping

1) Field mapping

  • CodingKeysAn enumeration class defined within a class follows Stringand CodingTableKey;
  • Write the variables you want to store behind CodingKeysin the enumeration case, indicating that they are bound to the fields in the database table;
  • 把需要在数据库重命名的字段,进行别名映射, 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 的代码,现在使用有些需要修改,会提示:

Pasted Graphic 1.png

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)
复制代码

数据库表中如下:

1__#$!@%!#__Pasted Graphic 1.png

运行打印:

startTime ------------------ 12:05:43
endTime   ------------------ 12:06:23
复制代码

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

2) Transaction insert operation

// 事务插入,性能较好
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)
复制代码

Running prints:

startTime ------------------ 12:14:42
endTime   ------------------ 12:14:55
复制代码

Transaction operation, inserting 1 million pieces of data takes about 13 seconds, and the performance has been significantly improved.

3) Built-in transaction insert operation

insert(objects:)The interface has built-in transactions, and targeted optimization of batch data, with better performance

do{
    try personTable?.insert(objects: persons)
}catch let error{
    debugPrint("插入数据失败 \(error.localizedDescription)")
}
let endTime = Self.getCurrentTime(timeFormat: TimeFormat.HHMMSS)
print("endTime ------------------" + endTime)
复制代码

Running prints:

startTime ------------------ 12:23:05
endTime   ------------------ 12:23:08
复制代码

It can be seen that the optimization of the built-in transaction interface is very obvious, and it only takes 3 seconds to insert 1 million pieces of data .

2. Atomicity

Under multi-threading, deleting data and inserting a piece of data at the same time, the operation is instantaneous, and it is difficult to determine which one will be executed first:

1) Non-transactional operations

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)")
}
复制代码

Result: Output 0 or 1 for a single call , 0 or 1 or 2 for multiple calls .

2) Transaction operation

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)")
}
复制代码

Result: only 1 will be output .

reference

Guess you like

Origin juejin.im/post/7218088415472189497