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

在Swift Storyboard中如何创建并打开SQLite数据库及数据表?

问题分析

你遇到的核心问题集中在三个点:

  1. 路径格式错误:使用url.absoluteString会得到带file://前缀的字符串,而sqlite3_open需要本地文件的纯路径字符串,这直接导致数据库文件没有被正确创建。
  2. 冗余的文件创建逻辑:createFile方法仅生成URL但未实际创建文件,且SQLite的sqlite3_open会自动在指定路径创建数据库文件,手动创建完全没必要。
  3. 初始化流程混乱:创建表、启用外键的时机未与数据库打开流程绑定,导致这些操作要么未执行,要么重复执行报错。
解决方案
  1. 修正数据库路径获取方式,用url.path替代absoluteString,传递正确的本地路径给SQLite
  2. 删除无用的createFile方法,依赖sqlite3_open自动创建数据库文件
  3. 将启用外键、创建表的逻辑绑定到数据库打开流程中,确保仅在数据库成功打开后执行
  4. 表创建语句中加入IF NOT EXISTS,避免重复创建表的报错
修改后的代码示例
public class SQLiteDatabase {

    public static var currentAccount = Account(id: -1, email: "", name: "", lastname: "", gender: "", birthday: Date(), creationDate: Date())
    public static let pathExtension = "xPose.sqlite"
    
    // 修正:用计算属性获取正确的数据库路径
    public static var databasePath: String {
        do {
            // 确保Documents目录存在,create设为true
            let url = try FileManager.default.url(for: .documentDirectory,
                                                 in: .userDomainMask,
                                                 appropriateFor: nil,
                                                 create: true)
                .appendingPathComponent(pathExtension)
            return url.path // 使用纯路径字符串,去掉file://前缀
        } catch {
            fatalError("无法获取数据库路径:\(error)")
        }
    }
    
    private let dbPointer: OpaquePointer?
    private init(dbPointer: OpaquePointer?) {
        self.dbPointer = dbPointer
    }
    
    deinit {
        sqlite3_close(dbPointer)
    }
    
    fileprivate var errorMessage: String {
        if let errorPointer = sqlite3_errmsg(dbPointer) {
            return String(cString: errorPointer)
        } else {
            return "SQLite未提供错误信息"
        }
    }
    
    func enableForeignKeys() {
        if sqlite3_exec(dbPointer, "PRAGMA foreign_keys = ON", nil, nil, nil) != SQLITE_OK {
            let err = String(cString: sqlite3_errmsg(dbPointer))
            print("启用外键失败:\(err)")
        }
    }
    
    func createTables() throws {
        try createTable(table: Account.self)
        try createTable(table: Measurement.self)
        try createTable(table: Posture.self)
        try createTable(table: Vital.self)
        try createTable(table: Mood.self)
    }
    
    // 修正:简化open方法,绑定初始化流程
    static func open() throws -> SQLiteDatabase {
        let path = databasePath
        var db: OpaquePointer?
        
        guard sqlite3_open(path, &db) == SQLITE_OK else {
            defer { db.map(sqlite3_close) }
            let errorMsg = db.flatMap { String(cString: sqlite3_errmsg($0)) } ?? "未知错误"
            throw SQLiteError.OpenDatabase(message: errorMsg)
        }
        
        let database = SQLiteDatabase(dbPointer: db)
        // 启用外键
        database.enableForeignKeys()
        // 创建表(需确保表语句带IF NOT EXISTS)
        try database.createTables()
        return database
    }
    
    func prepareStatement(sql: String) throws -> OpaquePointer? {
        var statement: OpaquePointer?
        guard sqlite3_prepare_v2(dbPointer, sql, -1, &statement, nil) == SQLITE_OK else {
            throw SQLiteError.Prepare(message: errorMessage)
        }
        return statement
    }
    
    func createTable(table: SQLTable.Type) throws {
        let createTableStatement = try prepareStatement(sql: table.createStatement)
        defer { sqlite3_finalize(createTableStatement) }
        
        guard sqlite3_step(createTableStatement) == SQLITE_DONE else {
            throw SQLiteError.Step(message: errorMessage)
        }
        print("\(table) 表创建成功。")
    }
}

// 注意:你的SQLTable协议的createStatement要加上IF NOT EXISTS,示例:
protocol SQLTable {
    static var createStatement: String { get }
}

extension Account: SQLTable {
    static var createStatement: String {
        """
        CREATE TABLE IF NOT EXISTS Account (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            email TEXT UNIQUE NOT NULL,
            name TEXT,
            lastname TEXT,
            gender TEXT,
            birthday DATETIME,
            creationDate DATETIME NOT NULL
        )
        """
    }
}
使用说明

在需要初始化数据库的地方(比如App启动时、ViewModel初始化时)调用:

do {
    let database = try SQLiteDatabase.open()
    // 这里可以开始使用数据库进行增删改查操作
} catch {
    print("数据库打开失败:\(error)")
}

内容的提问来源于stack exchange,提问作者Xa4

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 00:55:20