WCDB使用

WCDB使用看这篇就够了

我这儿WCDB使用的版本是2.0.4,我直接贴代码,可以直接看demo,详细用法和封装都写在demo里了,我是demo,查看数据库工具可以用这个,我是工具,提取码: 7jx7,工具下载下来可能安装可能显示已损坏,设置-安全性与隐私-通用 里设置允许任何来源,没那个选项就命令行搞定,复制下面语句命令行执行再次查看。

sudo spctl --master-disable

WCDB增删改查

/*
 * 这儿对WCDB的操作打开了监听日志的,可留意控制台打印信息
 * 对WCDB的任何操作都依赖于模型绑定,模型首先要遵循TableCodable协议,需要存储的值需要在enum CodingKeys: String, CodingTableKey{}里面case 记录,static let objectRelationalMapping = TableBinding(CodingKeys.self) {}方法里设置你对数据库存储值的设定
 * WCDB数据库一般情况下不需要开发者手动调用开关。操作时自动打开,当没有指向 Database 所共享的 Core 时,数据库会自动关闭,并回收内存
 * 具体更多信息自己阅读WCDB文档
 */

//MARK: - 增
private func addMenuItems() -> UIMenu {
    let addAction1 = UIAction.init(title: "insert") { _ in
        let object = Sample()
        object.identifier = 1
        object.description = "abc"
        //纯插入操作,由于设置了identifier为主键,所以identifier必须唯一,不然插入必失败并打印错误
        
        try? DBManager.shared.db.insert(object, intoTable: "\(Sample.self)")
    }
    let addAction2 = UIAction.init(title: "insertOrReplace") { _ in
        let object = Sample()
        object.identifier = 1
        object.description = "abcd"
        //当主键重复时即为更新,不重复时即直接插入数据
        
        try? DBManager.shared.db.insertOrReplace(object, intoTable: "\(Sample.self)")
    }
    let addAction3 = UIAction.init(title: "insertOrIgnore") { _ in
        let object = Sample()
        object.identifier = 1
        object.description = "abcdefg"
        //当出现主键重复时,直接忽略此操作,不重复时才插入
        
        try? DBManager.shared.db.insertOrIgnore(object, intoTable: "\(Sample.self)")
    }
    let addAction4 = UIAction.init(title: "带自定义对象insert") { _ in
        let object = Sample()
        //由于设置了主键identifier为自增,这儿identifier会视数据库中的最大值自增
        object.description = "自定义对象存储"
        guard let data = try? JSONEncoder().encode(["variable1": "1","variable2": "2"]) else {return}
        let cuModel = Customer(with: Value(data))
        object.myClass = cuModel
        //这儿的Customer自定义对象我们使用二进制存储,存储自定义对象只需要遵循ColumnCodable协议,且实现init?(with Value)和archivedValue方法,这两个方法相当于是解归档
        
        try? DBManager.shared.db.insert(object, intoTable: "\(Sample.self)")
    }
    let addAction5 = UIAction.init(title: "Sample设置了description不为空而传入空值") { _ in
        let object = Sample()
        object.identifier = 10
        //Sample设置了description不可为空,这样插入会失败且报错
        
        try? DBManager.shared.db.insert(object, intoTable: "\(Sample.self)")
    }
    return UIMenu.init(children: [addAction1,addAction2,addAction3,addAction4,addAction5])
}

//MARK: - 删
private func deleteMenuItems() -> UIMenu {
    let deleteAction1 = UIAction.init(title: "delete") { _ in
        //delete(fromTable table: String,where condition: Condition? = nil,orderBy orderList: [OrderBy]? = nil,limit: Limit? = nil,offset: Offset? = nil),这五个组合起来可以理解为:将 table 表内,满足 condition 的数据,按照 orderList 的方式进行排序,然后从头开始第 offset 行数据后的 limit 行数据删除,各参数代表的含义:table:表名,condition:条件,这儿可以多个条件合并,orderList:排序规则,这儿是数组,可以有多个列的排序规则,limit:无offset时理解为前 limit 行,有offset时理解为后 limit 行,offset:前 offset 行
        
        try? DBManager.shared.db.delete(fromTable: "\(Sample.self)",
                                         where: Sample.Properties.identifier > 2 && Sample.Properties.description == "abc",
                                         orderBy: [Sample.Properties.identifier.asOrder().order(.descending)],
                                         limit: 2,offset: 3)
    }
    
    return UIMenu.init(children: [deleteAction1])
}

//MARK: - 改
private func changeMenuItems() -> UIMenu {
    //propertyConvertibleList、condition、limit 和 offset 前面介绍过了,两个更新不同的在with参数,on后的参数代表需要更新的列
    let changeAction1 = UIAction.init(title: "基于对象的更新(update with object)") { _ in
        let object = Sample()
        object.description = "byObject"
        
        try? DBManager.shared.db.update(table: "\(Sample.self)",
                                         on: [Sample.Properties.description],
                                         with: object,
                                         where: Sample.Properties.identifier > 1)
    }
    let changeAction2 = UIAction.init(title: "基于值的更新(update with row)") { _ in
        let row: [ColumnCodable] = ["byRow"]
        //当on后的参数包含myClass而未设定myClass的值时,会置空
        
        try? DBManager.shared.db.update(table: "\(Sample.self)",
                                         on: [Sample.Properties.description,Sample.Properties.myClass],
                                         with: row,
                                         where: Sample.Properties.identifier == 10000)
    }
    
    return UIMenu.init(children: [changeAction1,changeAction2])
}

//MARK: - 查

/*
 * 若是部分列查询只获取了 identifier 字段,而没有获取 description 的值。这就可能与 Swift 本身存在冲突。 Swift 规定了对象创建时,必须初始化所有成员变量。而进行对象部分查询时,则可能出现某部分变量没有变查询,因此无法初始化的情况。因此,对于可能不被查询的成员变量,应将其类型定义为可选值。 对于 Sample 类中,"getObjects" 接口虽然没有获取 description 的值,但由于 description 是 String? 类型,因此不会出错。 而将`var description: String?` 改为 `var description: String`就会出错,这儿使用部分查询时模型绑定里的值一定要都设置为可选值
 * 只有getObjects和getObject为对象查询,其余都是值查询
 * "getRows" 接口获取整个矩阵的所有内容,即返回值为二维数组。
 * "getRow" 接口获取某一横行的数据,即返回值为一维数组。
 * "getColumn" 接口获取某一纵列的数据,即返回值为一维数组。
 * "getDistinctColumn" 与 "getColumn" 类似,但它会过滤掉重复的值。
 * "getValue" 接口获取矩阵中某一个格的内容。
 * "getDistinctValue" 与 "getValue" 类似,但它会过滤掉重复的值。
 */

private func checkMenuItems() -> UIMenu {
    let checkAction1 = UIAction.init(title: "getObjects(全部列的查询)") { _ in
        
        do {
            let allObjects: [Sample] = try DBManager.shared.db.getObjects(on: Sample.Properties.all, fromTable: "\(Sample.self)")
            print("getObjects(全部列的查询)",allObjects)
        } catch {

        }
    }
    let checkAction2 = UIAction.init(title: "getObject(全部列的查询)") { _ in
        
        do {
            //"getObject" 等价于 limit: 1 时的 "getObjects" 接口
            let object: Sample? = try DBManager.shared.db.getObject(on: Sample.Properties.all, fromTable: "\(Sample.self)")
            if let obj = object {
                print("getObject(全部列的查询)",obj.identifier,obj.description,obj.myClass)
            } else {
                print("getObject(全部列的查询)obj为空")
            }
        } catch {

        }
        
    }
    let checkAction3 = UIAction.init(title: "getObjects(部分列的查询)") { _ in
        
        do {
            let allObjects: [Sample] = try DBManager.shared.db.getObjects(on: [Sample.Properties.identifier,Sample.Properties.myClass], fromTable: "\(Sample.self)")
            print("getObjects(部分列的查询)",allObjects)
        } catch {

        }
    }
    let checkAction4 = UIAction.init(title: "getRows(全部列的查询)") { _ in
        
        do {
            let allRows = try DBManager.shared.db.getRows(on: Sample.Properties.all, fromTable: "\(Sample.self)")
            print("getRows(全部列的查询)",allRows)
            //row column 代表查询到的值里面第 row 行 第 column 列
            print("getRows(全部列的查询)",allRows[row: 0, column: 0].int64Value)
        } catch {
            
        }
    }
    let checkAction5 = UIAction.init(title: "getRow(全部列的查询)") { _ in
        
        do {
            //若是带 offset 参数表示查询第 offset+1 行
            let row = try DBManager.shared.db.getRow(on: Sample.Properties.all, fromTable: "\(Sample.self)",offset: 1)
            print("getRow(全部列的查询)",row)
            print("getRow(第二行第1列值的查询)",row[1].stringValue)
        } catch {
            
        }
    }
    let checkAction6 = UIAction.init(title: "getColumn(部分列的查询)") { _ in
        
        do {
            //获取 description 列
            let descriptionColumn = try DBManager.shared.db.getColumn(on: Sample.Properties.description, fromTable: "\(Sample.self)")
            print("getColumn(description列的查询)",descriptionColumn)
            print("getColumn(description列第0个的查询)",descriptionColumn[0].stringValue)
        } catch {
            
        }
    }
    let checkAction7 = UIAction.init(title: "getDistinctColumn(部分列的查询)") { _ in
        
        do {
            //获取 description 列
            let distinctDescriptionColumn = try DBManager.shared.db.getDistinctColumn(on: Sample.Properties.description, fromTable: "\(Sample.self)")
            print("getDistinctColumn(description列的查询)",distinctDescriptionColumn)
            print("getDistinctColumn(description列第0个的查询)",distinctDescriptionColumn[0].stringValue)
        } catch {
            
        }
    }
    let checkAction8 = UIAction.init(title: "getValue(部分列的查询)") { _ in
        
        do {
            //获取 description 列,无offset参数代表第一行,有即 offset+1行
            let value = try DBManager.shared.db.getValue(on: Sample.Properties.description, fromTable: "\(Sample.self)")
            print("getValue(description列第一行的查询)",value)
            print("getValue(description列第一行值的查询)",value.stringValue)
        } catch {
            
        }
    }
    let checkAction9 = UIAction.init(title: "getValue(identifier最大值的查询)") { _ in
        
        do {
            //获取 identifier最大值
            let value = try DBManager.shared.db.getValue(on: Sample.Properties.identifier.max(), fromTable: "\(Sample.self)")
            print("getValue(identifier最大值的查询)",value)
            print("getValue(identifier最大值的查询)",value.int64Value)
        } catch {
            
        }
    }
    let checkAction10 = UIAction.init(title: "getDistinctValue(不重复的description查询)") { _ in
        
        do {
            //获取不重复的description的值
            let value = try DBManager.shared.db.getDistinctValue(on: Sample.Properties.description, fromTable: "\(Sample.self)")
            print("getDistinctValue(不重复的description查询)",value)
            print("getDistinctValue(不重复的description查询)",value.stringValue)
        } catch {
            
        }
    }
    let checkAction11 = UIAction.init(title: "联表查询prepareMultiSelect(需要先执行可中断事务,在另一张sampleTable插入些数据)") { _ in
        
        do {
            //这儿联表查询需要查哪些列的只能单独列出来,不能用.all
            let multiSelect = try DBManager.shared.db.prepareMultiSelect(on: [Sample.Properties.identifier.in(table: "\(Sample.self)"),
                                                                              Sample.Properties.description.in(table: "\(Sample.self)"),
                                                                              Sample.Properties.identifier.in(table: "sampleTable"),
                                                                              Sample.Properties.description.in(table: "sampleTable")],
                                                                         fromTables: ["\(Sample.self)","sampleTable"])
                .where(Sample.Properties.identifier.in(table: "\(Sample.self)") == Sample.Properties.identifier.in(table: "sampleTable"))
            while let multiObject = try multiSelect.nextMultiObject() {
                let sample = multiObject["\(Sample.self)"] as? Sample
                let otherSample = multiObject["sampleTable"] as? Sample
                print("联表查询",sample,otherSample)
            }
        } catch {
            
        }
    }
    
    return UIMenu.init(children: [checkAction1,checkAction2,checkAction3,checkAction4,checkAction5,checkAction6,checkAction7,checkAction8,checkAction9,checkAction10,checkAction11])
}

//MARK: - 表

/* 表相当于指定了表名和模型绑定类的 Database,其实质只是后者的简化版。增删查改中提到的所有接口Table都具备,而且这些接口调用时都不需要再传表名和 ORM 类型,因为执行数据读写时Table使用起来比Database更加简洁,而且也有利于以表为单位来管理数据读写逻辑,所以WCDB推荐尽量使用Table来进行数据读写。
 */

private func tableMenuItems() -> UIMenu {
    //下面增删改查分别写个示例
    
    let table = DBManager.shared.db.getTable(named: "\(Sample.self)",of: Sample.self)
    let tableAction1 = UIAction.init(title: "增") { _ in
        let object = Sample()
        object.identifier = 55
        object.description = "增"
        try? table.insert(object)
    }
    let tableAction2 = UIAction.init(title: "删") { _ in
        try? table.delete(limit: 1)
    }
    let tableAction3 = UIAction.init(title: "改") { _ in
        let object = Sample()
        object.description = "改"
        try? table.update(on: Sample.Properties.description, with: object,where: Sample.Properties.identifier > 55)
    }
    let tableAction4 = UIAction.init(title: "查") { _ in
        do {
            let objects:[Sample] = try table.getObjects(on: Sample.Properties.all)
            print("表查询",objects)
        } catch {
            
        }
    }
    
    return UIMenu.init(children: [tableAction1,tableAction2,tableAction3,tableAction4])
}

//MARK: - 事务

/*
 * 事务一般用于 提升性能 和 保证数据原子性。Database 和 Table 都能直接发起事务,也可以通过 Transaction 更好地控制事务
 * 事务提升性能的实质是批量处理
 * 在多线程下,删除操作发生的时机是不确定的。倘若它发生在 插入完成之后 和 取出数据之前 的瞬间,则 getObjects() 无法取出刚才插入的数据,且这种多线程低概率的 bug 是很难查的。而事务可以保证一段操作的原子性
 */

private func transactionMenuItems() -> UIMenu {
    
    let table = DBManager.shared.db.getTable(named: "\(Sample.self)",of: Sample.self)
    let transAction1 = UIAction.init(title: "多个对象单独插入") { _ in
        let object = Sample()
        object.description = "多个对象单独插入"
        let objects = Array(repeating: object, count: 2000)
        
        for object in objects {
            try? table.insert(object)
        }
    }
    let transAction2 = UIAction.init(title: "多个对象事务插入") { _ in
        let object = Sample()
        object.description = "多个对象事务插入"
        let objects = Array(repeating: object, count: 2000)
        
        //insert(objects:) 接口内置了事务,并对批量数据做了针对性的优化,性能更好
        try? DBManager.shared.db.run(transaction: { _ in
            for object in objects {
                try? table.insert(object)
            }
        })
    }
    //在多线程下,删除操作发生的时机是不确定的。倘若它发生在 插入完成之后 和 取出数据之前 的瞬间,则 getObjects() 无法取出刚才插入的数据,且这种多线程低概率的 bug 是很难查的。
    let transAction3 = UIAction.init(title: "插入查询不写在事务中") { _ in
        DispatchQueue(label: "other thread").async {
            try? table.delete()
        }
        let object = Sample()
        object.description = "不写在事务中"
        try? table.insert(object)
        do {
            let objects = try table.getObjects(on: Sample.Properties.all)
            print("插入查询不写在事务中",objects.count) // 值不固定说明先后顺序不确定
        } catch {
            
        }
        
    }
    let transAction4 = UIAction.init(title: "插入查询都写在事务中") { _ in
        DispatchQueue(label: "other thread").async {
            try? table.delete()
            try? DBManager.shared.db.run(transaction: { _ in
                let object = Sample()
                object.description = "都写在事务中"
                try? table.insert(object)
                do {
                    let objects = try table.getObjects(on: Sample.Properties.all)
                    print("插入查询都写在事务中",objects.count) // 输出1
                } catch {
                    
                }
            })
        }
        
    }
    //WCDB Swift 提供了四种事务,普通事务、可控事务、嵌入事务和可中断事务
    //上面的transAction1和transAction2皆为普通事务
    let transAction5 = UIAction.init(title: "可控事务") { _ in
        let object = Sample()
        object.description = "可控事务"
        let objects = Array(repeating: object, count: 10)
        //由于事务是批量处理,所以i == 3返回 false即回滚了数据,也就一个数据都没插入
        try? DBManager.shared.db.run(controllableTransaction: { _ in
            for i in 0...objects.count-1 {
                let obj = objects[i]
                if i == 3 {
                    return false
                }
                try? table.insert(obj)
            }
            return true
        })
    }
    
    let transAction6 = UIAction.init(title: "嵌入事务") { _ in
        let object = Sample()
        object.description = "嵌入事务"
        let objects = Array(repeating: object, count: 10)
        try? DBManager.shared.db.run(transaction: { _ in
            try? DBManager.shared.db.run(controllableTransaction: { _ in
                for i in 0...objects.count-1 {
                    let obj = objects[i]
                    if i == 3 {
                        return false
                    }
                    try? table.insert(obj)
                }
                return true
            })
        })
    }
    //在需要对数据库进行大量数据更新的场景,我们的开发习惯一般是将这些更新操作统一到子线程处理,这样可以避免阻塞主线程,影响用户体验。为了解决大事务会阻塞主线程的问题,WCDB 才加入了可中断事务。可中断事务把一个流程很长的事务过程看成一个循环逻辑,每次循环执行一次短时间的DB操作。操作之后根据外部传入的参数判断当前事务是否可以结束,如果可以结束的话,就直接Commit Transaction,将事务修改内容写入磁盘。如果事务还不可以结束,再判断主线程是否因为当前事务阻塞,没有的话就回调外部逻辑,继续执行后面的循环,直到外部逻辑处理完毕。如果检测到主线程因为当前事务阻塞,则会立即 Commit Transaction,先将部分修改内容写入磁盘,并唤醒主线程执行DB操作。等到主线程的DB操作执行完成之后,在重新开一个新事务,让外部可以继续执行之前中断的逻辑。
    let transAction7 = UIAction.init(title: "可中断事务") { _ in
        var objects: [Sample] = []
        for i in 0..<100 {
            let obj = Sample()
            obj.identifier = i
            obj.description = "可中断事务\(i)"
            objects.append(obj)
        }

        DispatchQueue(label: "other thread").async {
            do {
                var index = 0
                try DBManager.shared.db.run(pausableTransaction: { handle, stop, isNewTransaction in
                    // isNewTransaction表示第一次执行,或者事务在上次循环结束之后被中断提交了
                    if isNewTransaction {
                        //新事务先建一下表,避免事务被中断之后,表已经被其他逻辑删除
                        try handle.create(table: "sampleTable", of: Sample.self)
                    }
                    //写入一个对象,这里还可以用PreparedStatement来减少SQL解析的耗时
                    try handle.insert(objects[index], intoTable: "sampleTable")
                    
                    index += 1
                    //给stop赋值成true表示事务结束
                    stop = index >= (objects.count-3)
                })
            } catch {
                print("Transaction failed with error: \(error)")
            }
        }
    }
    
    return UIMenu.init(children: [transAction1,transAction2,transAction3,transAction4,transAction5,transAction6,transAction7])
}

//MARK: - 语言集成查询

/*
 * 语言集成查询使得开发者能够通过 Swift 的语法特性去完成 SQL 语句。
 */

private func integratedQueryMenuItems() -> UIMenu {
   
    let integratedQueryAction1 = UIAction.init(title: "增") { _ in
        let statementInsert = StatementInsert().insert(intoTable: "\(Sample.self)").columns(Sample.Properties.identifier).values(99)
        print(statementInsert.description) // 输出 "INSERT INTO Sample(identifier) VALUES(99)"
    }
    let integratedQueryAction2 = UIAction.init(title: "删") { _ in
        let statementInsert = StatementDelete().delete(from: "\(Sample.self)").where(Sample.Properties.identifier > 5)
        print(statementInsert.description) // 输出 "DELETE FROM Sample WHERE id > 5"
    }
    let integratedQueryAction3 = UIAction.init(title: "改") { _ in
        let statementInsert = StatementUpdate().update(table: "\(Sample.self)").where(Sample.Properties.identifier == 1).set(Sample.Properties.description).to("语言集成查询改值")
        print(statementInsert.description) // 输出 "UPDATE Sample SET description = '语言集成查询改值' WHERE id == 1"
    }
    let integratedQueryAction4 = UIAction.init(title: "查") { _ in
        let statementInsert = StatementSelect().select(Sample.Properties.description).from("\(Sample.self)").where(Sample.Properties.identifier > 5)
        print(statementInsert.description) // 输出 "SELECT description FROM Sample WHERE id > 5"
    }
    
    
    return UIMenu.init(children: [integratedQueryAction1,integratedQueryAction2,integratedQueryAction3,integratedQueryAction4])
}

//MARK: - Other

/*
 * 数据库升级
 *
 * WCDB的数据库升级很简单,我们知道不销毁表的情况下,无法对列直接进行删除,所以WCDB做了这样的处理
 * 直接在模型绑定里进行列的是否存储操作,新加列可以在case 里加上,当db.create(table:of:)调用时会自动在表里新增列,'删除'列可在case 里删除,当db.create(table:of:)调用时会自动在表里忽略此列,但是表里此列并没有删除,且以前此列的值不会删除
 */


/*
 * 数据库操作
 *
 * database.purge()即回收 database 数据库中暂不使用的内存
 * Database.purge()即回收所有已创建的数据库中暂不使用的内存
 * 在 iOS 平台上,当内存不足、收到系统警告时,WCDB Swift 会自动调用 Database.purge()  接口以减少内存占用
 * 某些情况下,开发者需要确保数据库完全关闭后才能进行操作,如移动文件操作
 * 可以使用以下方式保证是在WCDB数据库关闭后做的操作
 * try? database.close(onClosed: {
 *     try database.moveFiles(toDirectory: otherDirectory)
 * })
 */


/*
 * 文件与代码模版
 *
 * 模型绑定的大部分都是格式固定的代码,因此,WCDB Swift 提供了文件模版和代码模版两种方式,以简化模型绑定操作。 文件和代码模版都在源代码的 tools/templates 目录下
 * 这儿我们使用文件模版,首先获取 WCDB 的 Github 仓库,在命令行如下操作:
 * cd path-to-your-wcdb-dir/tools/templates        //进入wcdb项目里找到tools->templats目录,拖进去
 * sh install.sh                                   //这儿是把文件工具导入Xcode创建新文件里
 * 文件模版安装完成后,在 Xcode 的菜单 File -> New -> File... 中创建新文件,通用数据模版选择 TableCodable。 在弹出的菜单中输入文件名,并选择 Language 为 Swift 即可。
 * 自定义类型模版选择 ColumnCodable
 */

WCDB推荐使用表操作,我有封装,可直接使用

class DBManager: NSObject {
    
    private let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0].appending("/myWCDB.db")
    
    private override init() {
        db = Database(at: path)
        print("WCDB数据库地址",path)

        //全局性能监控
        Database.globalTrace { tag, path, handleId, sql, cost in
            print("WCDB数据库性能指标: tag \(tag) id \(handleId) at path \(path) takes \(cost) seconds to execute sql \(sql)");
        }
        
        //全局错误监控
        Database.globalTrace(ofError: { (error: WCDBError) in
            #if DEBUG
            assert(error.level != .Fatal)
            #endif
            
            if error.level == .Ignore {
                print("可忽略WCDB数据库信息",error)
            } else {
                print("WCDB数据库错误",error)
            }
        })
        
        db.setNotification { corruptedDatabase in
            print("Database is corrupted: tag \(corruptedDatabase.tag ?? 0), path \(corruptedDatabase.path)")
            //WCDB 检测到损坏之后,isAlreadyCorrupted会始终返回 YES
            print("WCDB数据库有损坏",corruptedDatabase.isAlreadyCorrupted())// 输出1
        }
    }
    
    static let shared:DBManager = DBManager.init()
    
    public var db:Database!
    
    //MARK: - 创建表
    public func createTable<T:TableCodable>(name:String? = nil, model:T.Type) {
        try? db.create(table: name ?? "\(T.self)", of: T.self)
    }
  
    //MARK: - 增
    public func insertOrReplace<T:TableCodable>(_ objects:T..., tableName:String? = nil, on propertyConvertibleList: [PropertyConvertible]? = nil) {
        let table = db.getTable(named: tableName ?? "\(T.self)", of: T.self)
        try? table.insertOrReplace(objects, on: propertyConvertibleList)
    }
    
    public func insertOrIgnore<T:TableCodable>(_ objects:T..., tableName:String? = nil, on propertyConvertibleList: [PropertyConvertible]? = nil) {
        let table = db.getTable(named: tableName ?? "\(T.self)", of: T.self)
        try? table.insertOrIgnore(objects, on: propertyConvertibleList)
    }
    
    //MARK: - 删
    public func delete<T:TableCodable>(_ model:T.Type,
                                       tableName:String? = nil,
                                       where condition: Condition? = nil,
                                       orderBy orderList: [OrderBy]? = nil,
                                       limit: Limit? = nil,
                                       offset: Offset? = nil) {
        let table = db.getTable(named: tableName ?? "\(model.self)",of: model.self)
        try? table.delete(where: condition, orderBy: orderList, limit: limit, offset: offset)
    }
    
    //MARK: - 改
    public func update<T:TableCodable>(tableName:String? = nil,
                                       on propertyConvertibleList: PropertyConvertible...,
                                       with object: T,
                                       where condition: Condition? = nil,
                                       orderBy orderList: [OrderBy]? = nil,
                                       limit: Limit? = nil,
                                       offset: Offset? = nil) {
        let table = db.getTable(named: tableName ?? "\(T.self)",of: T.self)
        try? table.update(on: propertyConvertibleList, with: object, where: condition, orderBy: orderList, limit: limit, offset: offset)
    }
    
    //MARK: - 查
    public func getObjects<T:TableCodable>(_ model:T.Type,
                                           tableName:String? = nil,
                                           where condition: Condition? = nil,
                                           orderBy orderList: [OrderBy]? = nil,
                                           limit: Limit? = nil,
                                           offset: Offset? = nil) -> [T] {
        let table = db.getTable(named: tableName ?? "\(model.self)",of: model.self)
        do {
            let objects:[T] = try table.getObjects(on: T.Properties.all, where: condition, orderBy: orderList, limit: limit, offset: offset)
            return objects
        } catch {
            return []
        }
    }
    
    public func getObject<T:TableCodable>(_ model:T.Type,
                                           tableName:String? = nil,
                                           where condition: Condition? = nil,
                                           orderBy orderList: [OrderBy]? = nil,
                                           offset: Offset? = nil) -> T? {
        let table = db.getTable(named: tableName ?? "\(model.self)",of: model.self)
        do {
            let object:T? = try table.getObject(on: T.Properties.all, where: condition, orderBy: orderList, offset: offset)
            return object
        } catch {
            return nil
        }
    }
    
    
}

WCDB通用模型

/*
 * 模型绑定可参照SampleORM,模型绑定类型可为class,struct,enum
 *
 * 字段映射的类型,并非所有类型的变量都支持被绑定为字段。WCDB Swift 内建了常用类型的支持,包括:
 * 32 位整型    Bool, Int, Int8, Int16, Int32, UInt, UInt8, UInt16, UInt32
 * 64 位整型    Int64, UInt64, Date
 * 浮点型    Float, Double
 * 字符串类型    String, URL
 * 二进制类型    Data, Array, Dictionary, Set
 * 其中 Date 以时间戳的形式存储, Array、Dictionary、Set 以 JSON 的形式存储。
 * 对于没有内建支持的类型,可以手动为其添加支持,参照Customer
 *
 */

final class Sample: TableCodable {
    //模型绑定类型最好都设置为可选值,不然部分查询容易出错
    var identifier: Int? = nil
    var description: String? = nil
    var myClass: Customer? = nil
    
    var multiUniquePart1: Int? = nil
    var multiUniquePart2: Int? = nil
    
    //这一块管WCDB在数据库需要存储的值
    enum CodingKeys: String, CodingTableKey {
        typealias Root = Sample
        //这样写是重命名数据库中存储的列名,就是实际值是id不是identifier
        case identifier = "id"
        case description
        case myClass
        
        case multiUniquePart1
        case multiUniquePart2
        
        //这一块是配置数据库存储值的设定
        static let objectRelationalMapping = TableBinding(CodingKeys.self) {
            /*
             * BindColumnConstraint(
             * _ codingKey: CodingTableKeyType,// 对应字段的枚举
             * isPrimary: Bool = false, // 该字段是否为主键。字段约束中只能同时存在一个主键
             * orderBy term: OrderTerm? = nil, // 当该字段是主键时,存储顺序是升序还是降序
             * isAutoIncrement: Bool = false, // 当该字段是主键时,其是否支持自增。只有整型数据可以定义为自增。
             * onConflict conflict: Conflict? = nil, // 当该字段是主键时,若产生冲突,应如何处理
             * isNotNull: Bool = false, // 该字段是否可以为空
             * isUnique: Bool = false, // 该字段是否可以具有唯一性
             * defaultTo defaultValue: ColumnDef.DefaultType? = nil // 该字段在数据库内使用什么默认值)
             */
            
            //设置列的配置,WCDB里表主键只能设置一个
            BindColumnConstraint(identifier, isPrimary: true)
            //设置索引,索引名为表名+subfix(即这儿_uniqueIndex), isUnique:是否唯一(唯一索引就是字段的值不能有重复的两行出现)
            //列同时为主键和列的唯一索引,主键优先级大于唯一索引
            BindIndex(identifier, namedWith: "_uniqueIndex",isUnique: true)
            //对于需要特别指明索引存储顺序的字段,可以通过 asIndex(orderBy:) 函数指定
            BindIndex(description.asIndex(orderBy: .descending), namedWith: "_descendingIndex")
            //对于由多个字段组成的联合索引,BindIndex后面可以指定多个字段
            BindIndex(identifier, description.asIndex(orderBy: .descending), namedWith: "_multiIndex")
            //联合主键约束
//            BindMultiPrimary(multiUniquePart1,multiUniquePart2.asIndex(orderBy: .descending))
            //联合唯一约束
//            BindMultiUnique(multiUniquePart1,multiUniquePart2)
            //检查约束,有此约束,必须满足里面条件的数据才可插入,否则报错
//            BindChecks {
//                multiUniquePart2 > 3
//            }
            //设置两表的联合外键,用于联表查询
            //点击了"可中断事务"后会有sampleTable,然后可以使用以下sql语句查询
            //select sampleTable.id as sample_id, sampleTable.description as sample_des from sampleTable join Sample on Sample.id = sampleTable.id
//            BindForeginKey(identifier, foreignKey: ForeignKey.init().references(with: "sampleTable").columns(Sample.Properties.identifier))
            
        }
    }
    //当主键需要自动递增时需设置下面两个属性
    //用于定义是否使用自增的方式插入
    var isAutoIncrement: Bool = false//这儿一定要设置为false,设置了初始值false一样递增,若是设置了true当插入相同主键时,会crash
    //用于获取自增插入后的主键值
    var lastInsertedRowID: Int64 = 0
    
}

自定义模型

class Customer: ColumnCodable {
    var variable1: String? = nil
    var variable2: String? = nil
    
    static var columnType: ColumnType {
        return .BLOB
    }

    required init?(with value: Value) {
        let data = value.dataValue
        guard data.count > 0 else {
            return nil
        }
        guard let dictionary = try? JSONDecoder().decode([String: String].self, from: data) else {
            return nil
        }
        variable1 = dictionary["variable1"] ?? ""
        variable2 = dictionary["variable2"] ?? ""
    }

    func archivedValue() -> Value {
        if let data = try? JSONEncoder().encode(["variable1": variable1,"variable2": variable2]) {
            return Value(data)
        }
        return Value(nil)
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 本地数据加密 由于项目涉及到一些用户隐私数据的存储,所以需要对保存在客户端本地的数据进行加密,以防止用户隐私数据在...
    codeKeeper阅读 5,620评论 2 2
  • WCDB是微信的一个开源数据库框架。 相对于FMDB来说,WCDB更快,而且用了ORM数据-模型绑定,使用更方便,...
    无悔zero阅读 1,592评论 0 2
  • ⚠️ 使用的类最好导入 import WCDBSwift ,否则会报错 pod 'WCDB.swift' 创建一...
    9岁就很6阅读 5,076评论 12 4
  • iOS WCDB使用 准备 简介 WCDB 是基于SQLCipher,而SQLCipher 又是基于SQLite....
    L63C阅读 1,475评论 0 0
  • 1. 安装:使用Cocoapods安装 2. 使用 2.1 实现 WCTTableCoding 协议 类的定义:类...
    蓝天白云_Sam阅读 981评论 0 0