You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 11:14:34