iOS Swift 本地存储相关

作者: A_rcher34 | 来源:发表于2020-07-09 10:57 被阅读0次

    FMDB数据库存储

    一、简单介绍

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

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

    • executeStatements:执行多条 sql。
    • executeQuery:执行查询语句。
    • executeUpdate:执行除查询以外的语句,如 create、drop、insert、delete、update。

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

    • inDatabase: 参数是一个闭包,在闭包里面可以获得 FMDatabase 对象。
    • inTransaction: 使用事务。

    (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

    相关文章

      网友评论

        本文标题:iOS Swift 本地存储相关

        本文链接:https://www.haomeiwen.com/subject/ocjaqktx.html