iOS Swift 本地存储相关
2020-07-09 本文已影响0人
A_rcher34
FMDB数据库存储
一、简单介绍
- 什么是FMDB
FMDB 是 iOS 平台的 SQLite 数据库框架。
FMDB 以 OC 的方式封装了 SQLite 的 C 语言的 API。 - FMDB 的优点
FMDB 对比苹果自带的 Core Data 框架,更加轻量级和灵活。使用起来更加面向对象,省去了很多麻烦、冗余的 C 语言代码。
FMDB 还提供了多线程安全的数据库操作方法,能有效地防止数据混乱。 - FMDB 三个主要的类
(1)FMDatabase:一个 FMDatabase 表示一个 sqlite 数据库,所有对数据库的操作都是通过这个类(线程不安全)。它有如下几个方法:
- executeStatements:执行多条 sql。
- executeQuery:执行查询语句。
- executeUpdate:执行除查询以外的语句,如 create、drop、insert、delete、update。
(2)FMDatabaseQueue:内部封装 FMDatabase 和串行 queue,用于多线程操作数据库,并且提供事务(线程安全)。
- inDatabase: 参数是一个闭包,在闭包里面可以获得 FMDatabase 对象。
- inTransaction: 使用事务。
(3)FMResultSet:查询的结果集。可以通过字段名称获取字段值。
二、安装配置
- 使用pod安装
pod 'FMDB'
- 在桥接头文件里添加
#import "FMDB.h"
-
在 Build Phases -> Link Binary With Libraries 中点击加号,添加 libsqlite3.0.tbd 到项目中来
三、工具类的封装和使用
- 封装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
}
}
}
}
}
- 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