Swift中SQLite多表通用增删改查实现:如何减少重复的数据处理逻辑
Swift中SQLite多表通用增删改查实现:如何减少重复的数据处理逻辑
兄弟,我太懂你这种重复写SQL操作的痛苦了——每个表一套插入、查询、删除,代码堆得像山,改个字段要改N个地方,简直是维护噩梦!别慌,咱们用Swift的协议+泛型就能搞定通用的CRUD逻辑,适配不同表、不同字段和数据类型,一步到位。
第一步:定义通用模型协议
首先,我们给所有表模型定一个规矩(协议),要求它们提供表名、字段名,还要实现「把自己绑定到SQL语句」和「从查询结果初始化自己」的方法。这样后续的通用操作就能基于这个协议来做。
import SQLite3 protocol SQLiteModel { /// 表名 static var tableName: String { get } /// 所有字段名(顺序要和数据库表一致) static var columnNames: [String] { get } /// 将当前模型的数据绑定到SQL预处理语句 func bind(to statement: OpaquePointer?) /// 从SQL查询结果行初始化模型 init?(from row: OpaquePointer?) }
第二步:让你的表模型遵守协议
拿你给的Food模型举例,按照协议要求实现对应的方法:
struct Food: SQLiteModel { // 对应数据库表名 static let tableName = "USER_FOODS" // 对应表的字段名,顺序要和数据库里的列一致 static let columnNames = ["NAME_OF_FOOD", "TYPE_OF_FOOD", "AMOUNT_OF_FOOD"] let nameOfFood: String let typeOfFood: Int let amountOfFood: String // 实现绑定逻辑,把模型数据对应到SQL的占位符 func bind(to statement: OpaquePointer?) { guard let stmt = statement else { return } sqlite3_bind_text(stmt, 1, (nameOfFood as NSString).utf8String, -1, nil) sqlite3_bind_int(stmt, 2, Int32(typeOfFood)) sqlite3_bind_text(stmt, 3, (amountOfFood as NSString).utf8String, -1, nil) } // 从查询结果行解析成模型 init?(from row: OpaquePointer?) { guard let row = row else { return nil } // 按字段顺序解析,注意sqlite的列索引从0开始 guard let nameCString = sqlite3_column_text(row, 0) else { return nil } nameOfFood = String(cString: nameCString) typeOfFood = Int(sqlite3_column_int(row, 1)) guard let amountCString = sqlite3_column_text(row, 2) else { return nil } amountOfFood = String(cString: amountCString) } }
同理,你的USER_TASKS、USER_APPTS、USER_FINANCES模型都可以按照这个方式实现协议,只需要替换表名、字段名和对应的数据绑定/解析逻辑就行。
第三步:写通用的SQLite管理类
接下来我们写一个SQLiteManager类,封装通用的插入、查询、删除方法,所有遵守SQLiteModel的模型都能直接用这些方法,不用再重复写SQL逻辑。
class SQLiteManager { private let db: OpaquePointer? /// 初始化数据库管理器 /// - Parameter dbPath: 数据库文件的路径 init(dbPath: String) { var db: OpaquePointer? = nil let openResult = sqlite3_open(dbPath, &db) if openResult != SQLITE_OK { print("数据库打开失败:\(String(cString: sqlite3_errmsg(db)!))") self.db = nil return } self.db = db } /// 析构时关闭数据库 deinit { if let db = db { sqlite3_close(db) } } // MARK: - 通用插入方法 func insert<T: SQLiteModel>(_ model: T) -> Bool { guard let db = db else { return false } // 动态生成插入SQL:根据模型的字段名和数量生成占位符 let placeholders = Array(repeating: "?", count: T.columnNames.count).joined(separator: ", ") let columns = T.columnNames.joined(separator: ", ") let insertSQL = "INSERT INTO \(T.tableName) (\(columns)) VALUES (\(placeholders));" var statement: OpaquePointer? = nil defer { sqlite3_finalize(statement) } // 确保语句最终被释放 // 预处理SQL语句 let prepareResult = sqlite3_prepare_v2(db, insertSQL, -1, &statement, nil) if prepareResult != SQLITE_OK { print("插入语句预处理失败:\(String(cString: sqlite3_errmsg(db)!))") return false } // 绑定模型数据 model.bind(to: statement) // 执行插入 let stepResult = sqlite3_step(statement) if stepResult != SQLITE_DONE { print("插入执行失败:\(String(cString: sqlite3_errmsg(db)!))") return false } print("成功插入数据到\(T.tableName)表") return true } // MARK: - 通用查询所有数据方法 func fetchAll<T: SQLiteModel>() -> [T] { guard let db = db else { return [] } let selectSQL = "SELECT * FROM \(T.tableName);" var statement: OpaquePointer? = nil defer { sqlite3_finalize(statement) } let prepareResult = sqlite3_prepare_v2(db, selectSQL, -1, &statement, nil) if prepareResult != SQLITE_OK { print("查询语句预处理失败:\(String(cString: sqlite3_errmsg(db)!))") return [] } var models: [T] = [] // 遍历查询结果行 while sqlite3_step(statement) == SQLITE_ROW { if let model = T(from: statement) { models.append(model) } } return models } // MARK: - 通用删除方法(按主键id删除,你可以根据需求修改条件) func delete<T: SQLiteModel>(byId id: Int) -> Bool { guard let db = db else { return false } // 这里假设所有表都有id主键,如果你的主键名不同,可以在协议里加个primaryKey属性 let deleteSQL = "DELETE FROM \(T.tableName) WHERE id = ?;" var statement: OpaquePointer? = nil defer { sqlite3_finalize(statement) } let prepareResult = sqlite3_prepare_v2(db, deleteSQL, -1, &statement, nil) if prepareResult != SQLITE_OK { print("删除语句预处理失败:\(String(cString: sqlite3_errmsg(db)!))") return false } sqlite3_bind_int(statement, 1, Int32(id)) let stepResult = sqlite3_step(statement) if stepResult != SQLITE_DONE { print("删除执行失败:\(String(cString: sqlite3_errmsg(db)!))") return false } print("成功删除\(T.tableName)表中id为\(id)的数据") return true } }
第四步:实际使用示例
现在你可以用这个管理器轻松操作任何遵守协议的模型了,代码简洁到爆炸:
// 1. 获取数据库路径(这里存在Documents目录下) guard let documentsDir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else { fatalError("无法获取Documents目录") } let dbPath = documentsDir + "/MyAppDatabase.sqlite" // 2. 初始化管理器 let dbManager = SQLiteManager(dbPath: dbPath) // 3. 插入Food数据 let apple = Food(nameOfFood: "Apple", typeOfFood: 1, amountOfFood: "100g") let insertSuccess = dbManager.insert(apple) print(insertSuccess ? "插入成功" : "插入失败") // 4. 查询所有Food数据 let allFoods = dbManager.fetchAll(Food.self) print("当前共有\(allFoods.count)条食品数据") // 5. 删除指定id的Food数据 let deleteSuccess = dbManager.delete(byId: 1) print(deleteSuccess ? "删除成功" : "删除失败")
一些进阶注意事项
- 数据类型扩展:如果你的模型有Double、Date等其他类型,只需要在
bind方法里对应使用sqlite3_bind_double、sqlite3_bind_int64(Date转时间戳),在初始化方法里用sqlite3_column_double、sqlite3_column_int64解析即可。 - 灵活查询条件:如果需要按特定条件查询(比如按类型查Food),可以给
fetchAll加个whereClause参数,动态拼接SQL。 - 线程安全:SQLite本身不是线程安全的,如果你在多线程环境下使用,建议给
SQLiteManager加个串行队列,所有数据库操作都在队列里执行,避免并发问题。 - 错误处理优化:上面的示例用了Bool返回结果,实际项目里可以用Swift的
Result类型或者抛出错误,让调用者更灵活地处理失败场景。
备注:内容来源于stack exchange,提问作者Matthew
相关产品推荐
相关产品推荐

