ios-需要掌握的知识点

iOS Swift 本地存储相关

2020-07-09  本文已影响0人  A_rcher34

FMDB数据库存储

一、简单介绍

  1. 什么是FMDB
    FMDB 是 iOS 平台的 SQLite 数据库框架。
    FMDB 以 OC 的方式封装了 SQLite 的 C 语言的 API。
  2. FMDB 的优点
    FMDB 对比苹果自带的 Core Data 框架,更加轻量级和灵活。使用起来更加面向对象,省去了很多麻烦、冗余的 C 语言代码。
    FMDB 还提供了多线程安全的数据库操作方法,能有效地防止数据混乱。
  3. FMDB 三个主要的类

(1)FMDatabase:一个 FMDatabase 表示一个 sqlite 数据库,所有对数据库的操作都是通过这个类(线程不安全)。它有如下几个方法:

(2)FMDatabaseQueue:内部封装 FMDatabase 和串行 queue,用于多线程操作数据库,并且提供事务(线程安全)。

(3)FMResultSet:查询的结果集。可以通过字段名称获取字段值。

二、安装配置

  1. 使用pod安装 pod 'FMDB'
  2. 在桥接头文件里添加#import "FMDB.h"
  3. 在 Build Phases -> Link Binary With Libraries 中点击加号,添加 libsqlite3.0.tbd 到项目中来


三、工具类的封装和使用

  1. 封装FMDB工具类
    为方便使用,我们首先创建一个数据库类(FMDBManager)
class ZCFMDBManager: NSObject {

    // 创建单例
    public static let shareManger = ZCFMDBManager()
    
    // 数据库名称
    private let dbName = "test.db"
    
    // 数据库地址
    lazy var dbURL: URL = {
        // 根据传入的数据库名称拼接数据库的路径
        let fileURL = try! FileManager.default
            .url(for: .applicationSupportDirectory, in: .userDomainMask,
                 appropriateFor: nil, create: true)
            .appendingPathComponent(dbName)
        print("数据库地址:", fileURL)
        return fileURL
    }()
     
    // FMDatabase对象(用于对数据库进行操作)
    lazy var db: FMDatabase = {
        let database = FMDatabase(url: dbURL)
        return database
    }()
     
    // FMDatabaseQueue对象(用于多线程事务处理)
    lazy var dbQueue: FMDatabaseQueue? = {
        // 根据路径返回数据库
        let databaseQueue = FMDatabaseQueue(url: dbURL)
        return databaseQueue
    }()
    
    
    // MARK: ---------------------- 以上为工具类内容,以下是工具类的方法示例 ----------------------
    
    
    // MARK: ---------------------- 表操作 ----------------------
    
    // 打开/创建数据库
    public func openDB() {
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            print("数据库打开成功!")
        } else {
            print("数据库打开失败: \(currentDB.lastErrorMessage())")
        }
        currentDB.close()
    }
    
    // 创建数据表
    public func createTable() {
        // 编写SQL语句(id: 主键  name和age是字段名)
        let sql = "CREATE TABLE IF NOT EXISTS User( \n" +
            "id INTEGER PRIMARY KEY AUTOINCREMENT, \n" +
            "name TEXT, \n" +
            "age INTEGER \n" +
        "); \n"
        
        // 执行SQL语句(注意点: 在FMDB中除了查询以外, 都称之为更新)
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if currentDB.executeUpdate(sql, withArgumentsIn: []){
                print("创建表成功")
            }else{
                print("创建表失败")
            }
        }
        currentDB.close()
    }
    
    // 删除数据表
    public func deleteTable() {
        // 编写SQL语句
        let sql = "DROP TABLE User;"
        
        // 执行SQL语句
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if currentDB.executeUpdate(sql, withArgumentsIn: []){
                print("删除表成功")
            }else{
                print("删除表失败")
            }
        }
        currentDB.close()
    }
    
    // 添加表属性
    public func addTableVar() {
        // 编写SQL语句
        let sql = "ALTER TABLE User ADD phone varchar(256);"
        
        // 执行SQL语句
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if currentDB.executeUpdate(sql, withArgumentsIn: []){
                print("添加表字段成功")
            }else{
                print("添加表字段失败")
            }
        }
        currentDB.close()
    }
    
    // MARK: ---------------------- 表数据操作 ----------------------
    
    // 表数据 插入
    public func insertData() {
        // 编写SQL语句
        let sql = "INSERT INTO User (name, age) VALUES ('hangge', 100);"
         
        // 执行SQL语句
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if currentDB.executeUpdate(sql, withArgumentsIn: []){
                print("插入成功")
            }else{
                print("插入失败")
            }
        }
        currentDB.close()
    }
    
    // 表数据 插入(预编译)
    public func insertData2() {
        // 编写SQL语句
        let sql = "INSERT INTO User (name, age) VALUES (?,?);"
         
        // 执行SQL语句
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if currentDB.executeUpdate(sql, withArgumentsIn: ["hangge", 100]){
                print("插入成功")
            }else{
                print("插入失败")
            }
        }
        currentDB.close()
    }
    
    // 表数据 更新
    public func updateData() {
        // 编写SQL语句
        let sql = "UPDATE User set name = 'hangge.com' WHERE id = 2;"
         
        // 执行SQL语句
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if currentDB.executeUpdate(sql, withArgumentsIn: []){
                print("更新成功")
            }else{
                print("更新失败")
            }
        }
        currentDB.close()
    }
    
    // 表数据 更新(预编译)
    public func updateData2() {
        // 编写SQL语句
        let sql = "UPDATE User set name = ? WHERE id = ?;"
         
        // 执行SQL语句
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if currentDB.executeUpdate(sql, withArgumentsIn: ["hangge.com", 1]){
                print("更新成功")
            }else{
                print("更新失败")
            }
        }
        currentDB.close()
    }
    
    // 表数据 删除
    public func deleteData() {
        // 编写SQL语句
        let sql = "DELETE FROM User WHERE id = 2;"
         
        // 执行SQL语句
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if currentDB.executeUpdate(sql, withArgumentsIn: []){
                print("删除成功")
            }else{
                print("删除失败")
            }
        }
        currentDB.close()
    }
    
    // 表数据 删除(预编译)
    public func deleteData2() {
        // 编写SQL语句
        let sql = "DELETE FROM User WHERE id = ?;"
         
        // 执行SQL语句
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if currentDB.executeUpdate(sql, withArgumentsIn: [3]){
                print("删除成功")
            }else{
                print("删除失败")
            }
        }
        currentDB.close()
    }
    
    // 表数据 查询
    public func queryData() {
        // 编写SQL语句
        let sql = "SELECT * FROM User WHERE id < 10"
         
        // 执行SQL语句
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if let res = currentDB.executeQuery(sql, withArgumentsIn: []){
                // 遍历输出结果
                while res.next() {
                    let id = res.int(forColumn: "id")
                    let name = res.string(forColumn: "name")!
                    let age = res.int(forColumn: "age")
                    print(id, name, age)
                }
            }else{
                print("查询失败")
            }
        }
        currentDB.close()
    }
    
    // 表数据 查询(预编译)
    public func queryData2() {
        // 编写SQL语句
        let sql = "SELECT * FROM User WHERE id < ?"
         
        // 执行SQL语句
        let currentDB = ZCFMDBManager.shareManger.db
        if currentDB.open() {
            if let res = currentDB.executeQuery(sql, withArgumentsIn: [10]){
                // 遍历输出结果
                while res.next() {
                    let id = res.int(forColumn: "id")
                    let name = res.string(forColumn: "name")!
                    let age = res.int(forColumn: "age")
                    print(id, name, age)
                }
            }else{
                print("查询失败")
            }
        }
        currentDB.close()
    }
    
    // MARK: ---------------------- 表数据高级操作 ----------------------
    
    // 使用事务模式 插入多条数据
    public func queueInsertData() {
        // 使用事务插入10条数据
        if let queue = ZCFMDBManager.shareManger.dbQueue {
            queue.inTransaction { db, rollback in
                do {
                    for i in 0..<10 {
                        try db.executeUpdate("INSERT INTO User (name, age) VALUES (?,?);",
                                             values: ["hangge", i])
                    }
                    print("插入成功!")
                } catch {
                    print("插入失败,进行回滚!(之前插入的内容全部撤回)")
                    rollback.pointee = true
                }
            }
        }
    }
    
}
  1. FMDB 工具类测试
    在ViewController中通过按钮调用此测试方法。
private func FMDBTest() {
    let alert = UIAlertController.init(title: "数据库操作", message: nil, preferredStyle: .actionSheet)
    alert.addAction(UIAlertAction.init(title: "创建/打开数据库", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.openDB()
    }))
    alert.addAction(UIAlertAction.init(title: "创建表", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.createTable()
    }))
    alert.addAction(UIAlertAction.init(title: "删除表", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.deleteTable()
    }))
    alert.addAction(UIAlertAction.init(title: "添加表字段", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.addTableVar()
    }))
    alert.addAction(UIAlertAction.init(title: "表插入", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.insertData()
    }))
    alert.addAction(UIAlertAction.init(title: "表插入(预编译)", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.insertData2()
    }))
    alert.addAction(UIAlertAction.init(title: "表更新", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.updateData()
    }))
    alert.addAction(UIAlertAction.init(title: "表更新(预编译)", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.updateData2()
    }))
    alert.addAction(UIAlertAction.init(title: "表删除", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.deleteData()
    }))
    alert.addAction(UIAlertAction.init(title: "表删除(预编译)", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.deleteData2()
    }))
    alert.addAction(UIAlertAction.init(title: "表查询", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.queryData()
    }))
    alert.addAction(UIAlertAction.init(title: "表查询(预编译)", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.queryData2()
    }))
    alert.addAction(UIAlertAction.init(title: "表多条插入", style: .default, handler: { (_) in
        ZCFMDBManager.shareManger.queueInsertData()
    }))
    alert.addAction(UIAlertAction.init(title: "取消", style: .cancel, handler: nil))
    self.present(alert, animated: true, completion: nil)
}

四、实现实体类与数据库表的关联映射
创建一个基类(ZCFMDBModel)
(1)只要我们让实体类继承 ZCFMDBModel,那么它就会自行将实体类跟数据库表作关联映射。之后只要操作实体类对象就可以直接进行数据的读取,修改,新增操作。
(2)ZCFMDBModel 其内部实现使用到了 Swift 的反射,通过遍历对象内的所有属性,从而自动拼接成相应的 SQL 语句来执行。
(3)ZCFMDBModel 代码如下:

protocol ZCFMDBModelProtocol {}
 
// 数据库模型(一张表对应一个模型)
@objcMembers
class ZCFMDBModel: NSObject, ZCFMDBModelProtocol {
     
    // 模型对应的表名(直接使用对应模型类名字)
    internal var table = ""
     
    // 记录每个模式对应的数据表是否已经创建完毕了
    private static var verified = [String:Bool]()
     
    // 初始化方法
    required override init() {
        super.init()
        // 自动初始化表名
        self.table = type(of: self).table
        // 自动建对应的数据库表
        let verified = ZCFMDBModel.verified[self.table]
        if verified == nil || !verified! {
            let db = ZCFMDBManager.shareManger.db
            var sql = "CREATE TABLE IF NOT EXISTS \(table) ("
            // Columns
            let cols = values()
            var first = true
            for col in cols {
                if first {
                    first = false
                    sql += getColumnSQL(column:col)
                } else {
                    sql += ", " + getColumnSQL(column: col)
                }
            }
            // Close query
            sql += ")"
            if db.open() {
                if db.executeUpdate(sql, withArgumentsIn:[]) {
                    ZCFMDBModel.verified[table] = true
                    print("\(table) 表自动创建成功")
                }
            }
        }
    }
     
    // 返回主键字段名(如果模型主键不是id,则需要覆盖这个方法)
    func primaryKey() -> String {
        return "id"
    }
     
    // 忽略的属性(模型中不需要与数据库表进行映射的字段可以在这里发返回)
    func ignoredKeys() -> [String] {
        return []
    }
     
    // 静态方法返回表名
    static var table:String {
        // 直接返回类名字
        return "\(classForCoder())"
    }
     
    // 删除指定数据(可附带条件)
    @discardableResult
    class func remove(filter: String = "") -> Bool {
        let db = ZCFMDBManager.shareManger.db
        var sql = "DELETE FROM \(table)"
        if !filter.isEmpty {
            // 添加删除条件
            sql += " WHERE \(filter)"
        }
        if db.open() {
            return db.executeUpdate(sql, withArgumentsIn:[])
        } else {
            return false
        }
    }
     
    // 获取数量(可附带条件)
    class func count(filter: String = "") -> Int {
        let db = ZCFMDBManager.shareManger.db
        var sql = "SELECT COUNT(*) AS count FROM \(table)"
        if !filter.isEmpty {
            // 添加查询条件
            sql += " WHERE \(filter)"
        }
        if let res = db.executeQuery(sql, withArgumentsIn: []) {
            if res.next() {
                return Int(res.int(forColumn: "count"))
            } else {
                return 0
            }
        }
        return 0
    }
     
    // 保存当前对象数据
    // * 如果模型主键为空或者使用该主键查不到数据则新增
    // * 否则的话则更新
    @discardableResult
    func save() -> Bool{
        let key = primaryKey()
        let data = values()
        var insert = true
        let db = ZCFMDBManager.shareManger.db
         
        if let rid = data[key] {
            var val = "\(rid)"
            if rid is String {
                val = "'\(rid)'"
            }
            let sql = "SELECT COUNT(*) AS count FROM \(table) "
                + "WHERE \(primaryKey())=\(val)"
            if db.open() {
                if let res = db.executeQuery(sql, withArgumentsIn: []) {
                    if res.next() {
                        insert = res.int(forColumn: "count") == 0
                    }
                }
            }
             
        }
         
        let (sql, params) = getSQL(data:data, forInsert:insert)
        // 执行SQL语句
         
        if db.open() {
            return db.executeUpdate(sql, withArgumentsIn: params ?? [])
        } else {
            return false
        }
    }
     
    // 删除当天对象数据
    @discardableResult
    func delete() -> Bool{
        let key = primaryKey()
        let data = values()
        let db = ZCFMDBManager.shareManger.db
        if let rid = data[key] {
            if db.open() {
                let sql = "DELETE FROM \(table) WHERE \(primaryKey())=\(rid)"
                return db.executeUpdate(sql, withArgumentsIn: [])
            }
        }
        return false
    }
     
    // 通过反射获取对象所有有的属性和属性值
    internal func values() -> [String:Any] {
        var res = [String:Any]()
        let obj = Mirror(reflecting:self)
        processMirror(obj: obj, results: &res)
        getValues(obj: obj.superclassMirror, results: &res)
        return res
    }
     
    // 供上方方法(获取对象所有有的属性和属性值)调用
    private func getValues(obj: Mirror?, results: inout [String:Any]) {
        guard let obj = obj else { return }
        processMirror(obj: obj, results: &results)
        getValues(obj: obj.superclassMirror, results: &results)
    }
     
    // 供上方方法(获取对象所有有的属性和属性值)调用
    private func processMirror(obj: Mirror, results: inout [String: Any]) {
        for (_, attr) in obj.children.enumerated() {
            if let name = attr.label {
                // 忽略 table 和 db 这两个属性
                if name == "table" || name == "db" {
                    continue
                }
                // 忽略人为指定的属性
                if ignoredKeys().contains(name) ||
                    name.hasSuffix(".storage") {
                    continue
                }
                results[name] = unwrap(attr.value)
            }
        }
    }
     
    //将可选类型(Optional)拆包
    func unwrap(_ any:Any) -> Any {
        let mi = Mirror(reflecting: any)
        if mi.displayStyle != .optional {
            return any
        }
         
        if mi.children.count == 0 { return any }
        let (_, some) = mi.children.first!
        return some
    }
     
    // 返回新增或者修改的SQL语句
    private func getSQL(data:[String:Any], forInsert:Bool = true)
        -> (String, [Any]?) {
        var sql = ""
        var params:[Any]? = nil
        if forInsert {
            sql = "INSERT INTO \(table)("
        } else {
            sql = "UPDATE \(table) SET "
        }
        let pkey = primaryKey()
        var wsql = ""
        var rid:Any?
        var first = true
        for (key, val) in data {
            // 处理主键
            if pkey == key {
                if forInsert {
                    if val is Int && (val as! Int) == -1 {
                        continue
                    }
                } else {
                    wsql += " WHERE " + key + " = ?"
                    rid = val
                    continue
                }
            }
            // 设置参数
            if first && params == nil {
                params = [AnyObject]()
            }
            if forInsert {
                sql += first ? "\(key)" : ", \(key)"
                wsql += first ? " VALUES (?" : ", ?"
                params!.append(val)
            } else {
                sql += first ? "\(key) = ?" : ", \(key) = ?"
                params!.append(val)
            }
            first = false
        }
        // 生成最终的SQL
        if forInsert {
            sql += ")" + wsql + ")"
        } else if params != nil && !wsql.isEmpty {
            sql += wsql
            params!.append(rid!)
        }
        return (sql, params)
    }
     
    // 返回建表时每个字段的sql语句
    private func getColumnSQL(column:(key: String, value: Any)) -> String {
        let key = column.key
        let val = column.value
        var sql = "'\(key)' "
        if val is Int {
            // 如果是Int型
            sql += "INTEGER"
            if key == primaryKey() {
                sql += " PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE"
            } else {
                sql += " DEFAULT \(val)"
            }
        } else {
            // 如果是其它类型
            if val is Float || val is Double {
                sql += "REAL DEFAULT \(val)"
            } else if val is Bool {
                sql += "BOOLEAN DEFAULT " + ((val as! Bool) ? "1" : "0")
            } else if val is Date {
                sql += "DATE"
            } else if val is NSData {
                sql += "BLOB"
            } else {
                // Default to text
                sql += "TEXT"
            }
            if key == primaryKey() {
                sql += " PRIMARY KEY NOT NULL UNIQUE"
            }
        }
        return sql
    }
}
 
extension ZCFMDBModelProtocol where Self: ZCFMDBModel {
    // 根据完成的sql返回数据结果
    static func rowsFor(sql: String = "") -> [Self] {
        var result = [Self]()
        let tmp = self.init()
        let data = tmp.values()
        let db = ZCFMDBManager.shareManger.db
        let fsql = sql.isEmpty ? "SELECT * FROM \(table)" : sql
        if let res = db.executeQuery(fsql, withArgumentsIn: []){
            // 遍历输出结果
            while res.next() {
                let t = self.init()
                for (key, _) in data {
                    if let val = res.object(forColumn: key) {
                        t.setValue(val, forKey:key)
                    }
                }
                result.append(t)
            }
        }else{
            print("查询失败")
        }
        return result
    }
     
    // 根据指定条件和排序算法返回数据结果
    static func rows(filter: String = "", order: String = "",
                     limit: Int = 0) -> [Self] {
        var sql = "SELECT * FROM \(table)"
        if !filter.isEmpty {
            sql += " WHERE \(filter)"
        }
        if !order.isEmpty {
            sql += " ORDER BY \(order)"
        }
        if limit > 0 {
            sql += " LIMIT 0, \(limit)"
        }
        return self.rowsFor(sql:sql)
    }
}

创建具体的实体类

class TestUserBean: ZCFMDBModel {
    var id:Int = -1
    var name:String = ""
    var age:Int = -1
    
    // MARK: ---------------------- 修改主键 ----------------------
    /*
    var uid:Int = -1
    var name:String = ""
    var age:Int = -1
    
    // 返回主键名(如需修改主键,则重写此方法)
    override func primaryKey() -> String {
        return "uid"
    }
    */
    
    // MARK: ---------------------- 使某些属性不与数据库表进行映射 ----------------------
    /*
    // 默认情况下,模型里面所有属性都会创建对应的数据库表字段,如果想要某些属性不与数据库表进行映射,则需要重写覆盖 ignoredKeys() 方法:
    var id:Int = -1
    var name:String = ""
    var age:Int = -1
     
    // 返回忽略的键值
    override func ignoredKeys() -> [String] {
        return ["age"]
    }
    */
    
    // MARK: ---------------------- 新增数据样例 ----------------------
    // 下面创建 10 个模型对象,并调用 save() 方法保存到库中。
    // 注意:数据库以及数据表我们事先不需要手动创建,模型初始化时会自动判断是否存在对应的表,没有的话会自动创建。
    /*
    // 创建10条数据
    for i in 1..<11 {
        let user = TestUserBean()
        user.name = "测试用户\(i)"
        user.age = 100 + i
        user.save()
    }
    */
    
    // MARK: ---------------------- 修改数据样例 ----------------------
    /*
     更新数据同样是使用 save() 方法:
     如果模型对象主键为空或者该主键在表中查不到对应的记录,则为新增。
     否则的话则为修改。
     
    let user = User()
    user.id = 2
    user.name = "hangge.com"
    user.age = 0
    user.save()
     */
    
    // MARK: ---------------------- 删除数据 ----------------------
    /*
    // 调用模型对象的 delete() 方法即可将该对象对应的数据删除(内部根据主键来判断)
    let user = User()
    user.id = 2
    user.delete()
     
    // 也可以通过类方法 remove() 来批量删除数据:
    // 删除User表所有的数据
    User.remove()
     
    // 根据指定条件删除User表的数据
    User.remove(filter: "id > 5 and age < 107")
    */
    
    // MARK: ---------------------- 获取记录数 ----------------------
    /*
    // 通过类方法 count() 可以获取数据表中的所有记录数:
    let count = User.count()
    print("记录数:\(count)")

    // 我们也可根据指定条件来获取记录数:
    let count = User.count(filter: "id > 5 and age < 107")
    print("记录数:\(count)")
    */
    
    // MARK: ---------------------- 查询数据 ----------------------
    
    /*
    // 通过类方法 rows() 可以获取数据表中的所有数据(会自动转换成对应的模型数组):
     // 获取所有数据
     let users = User.rows()
              
     // 遍历输出结果
     for user in users {
         print(user.id, user.name, user.age)
     }
     
    // rows() 方法还可指定查询条件、排序、以及限制数量。
     // 根据条件获取数据
     let users = User.rows(filter: "id > 5 and age < 107", order: "age desc", limit: 3)
              
     // 遍历输出结果
     for user in users {
         print(user.id, user.name, user.age)
     }
     
    // 我们还可以使用 rowsFor() 方法来通过完整的 sql 查询数据(同样也会自动转换成对应的模型数组):
     // 通过sql获取数据
     let users = User.rowsFor(sql: "select * from User limit 3")
              
     // 遍历输出结果
     for user in users {
         print(user.id, user.name, user.age)
     }
     */
}

参考文献:
Swift - 第三方SQLite库FMDB使用详解1·
Swift - 第三方SQLite库FMDB使用详解2
Swift - 第三方SQLite库FMDB使用详解3
Swift - 第三方SQLite库FMDB使用详解4

上一篇 下一篇

猜你喜欢

热点阅读