swift 的 sqlite 不稳定

sqlite for swift is unstable

我已经设置 swift 项目来使用 sqlite。有时,插入时实际上并没有插入正确(或全部)的值。我知道,因为我重新启动了应用程序,当我回来时,条目要么是随机错误的(没有插入东西),要么是零。但有时是正确的。

这里是我设置的地方,是的,插入前数据的值是正确的。

let update = "INSERT INTO ToDoItem (itemName, completed, goalDate) " + "VALUES (?, ?, ?);"
var statement: COpaquePointer = nil
if sqlite3_prepare_v2(database, update, -1, &statement, nil) == SQLITE_OK {
    let itemName = item.itemName as String
    let completed = item.completed == true ? 1 : 0

    sqlite3_bind_text(statement, 1, itemName, -1, nil)
    sqlite3_bind_int(statement, 2, Int32(completed))

    if let goalDate = item.goalDate?.toString() {
        sqlite3_bind_text(statement, 3, goalDate, -1, nil)
    } else {
        sqlite3_bind_text(statement, 3, "", -1, nil)
    }

    //println("inserting \(itemName), \(completed) and \(item.goalDate?.toString())")
    //println("")
}

if sqlite3_step(statement) != SQLITE_DONE {
    println("error updateing table")
    sqlite3_close(database)
    return
}
sqlite3_finalize(statement)

sqlite3_close(database)

你可以看到中间被注释掉的 println,如果没有被注释掉,那么 itemName 有时会成为该字符串的一部分。

我遇到了同样的问题。我找到了解决这个问题的方法。

sqlite3_bind_text(statement, 1, itemName, -1, nil) --> itemName should be UTF8 String

您应该将 itemName 转换为 NSString 并使用 UTF8String 将您的字符串转换为 UTF8。右边代码同这里

let itemName = item.itemName as NSString
sqlite3_bind_text(statement, 1, itemName.UTF8String, -1, nil)

祝你好运。

此行为确实符合规范,错误在您的代码中。

您的问题根源在于您要传递给 sqlite3_bind_text 的 swift Stringsqlite3_bind_text 接受作为 const char* 的文本,在 Swift 中桥接为 UnsafePointer<CChar>。将 Swift String 传递给接受 UnsafePointer<CChar> 的函数时的行为记录在 Interacting with C APIs 的 "Constant Pointers" 部分中。它说:

The string will automatically be converted to UTF8 in a buffer, and a pointer to that buffer is passed to the function.

这可能是您希望发生的事情。

但是它还说:

The pointer passed to the function is guaranteed to be valid only for the duration of the function call. Don’t try to persist the pointer and access it after the function has returned.

这是您的错误的来源。 sqlite3_bind_text() 实际上可能会将指针保留在准备好的语句中,直到调用 sqlite3_finalize() 以便它可以在将来的 sqlite3_step() 调用中使用它。

另一个答案建议使用 NSString.UTF8String。它的寿命稍长一些,看起来应该足够了。 documentation 表示:

This C string is a pointer to a structure inside the string object, which may have a lifetime shorter than the string object and will certainly not have a longer lifetime. Therefore, you should copy the C string if it needs to be stored outside of the memory context in which you use this property.

"memory context"这里好像含糊不清。我不确定这是否意味着当前功能,或者直到当前 autoreleasepool 被耗尽,或者其他什么。如果它是前两个之一,那么您是安全的。如果没有,那么我会说你仍然是安全的,因为 NSString.UTF8String 似乎已经在这种情况下使用了很长时间而没有问题..

在swift中SQLite没有定义SQLITE_TRANSIENTSQLITE_STATIC,所以我们需要明确定义它。

swift 3 & 4

定义 属性 of SQLITE

let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self)
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)

SQL函数 在绑定文本 sqlite3_bind_text.

时添加 SQLITE_TRANSIENT 而不是 nil 作为最后一个参数
let update = "INSERT INTO ToDoItem (itemName, completed, goalDate) " + "VALUES (?, ?, ?);"
var statement: OpaquePointer = nil
if sqlite3_prepare_v2(database, update, -1, &statement, nil) == SQLITE_OK {
    let itemName = item.itemName as String
    let completed = item.completed == true ? 1 : 0

    sqlite3_bind_text(statement, 1, itemName, -1, SQLITE_TRANSIENT)
    sqlite3_bind_int(statement, 2, Int32(completed))

    if let goalDate = item.goalDate?.toString() {
        sqlite3_bind_text(statement, 3, goalDate, -1, SQLITE_TRANSIENT)
    } else {
        sqlite3_bind_text(statement, 3, "", -1, SQLITE_TRANSIENT)
    }

    //println("inserting \(itemName), \(completed) and \(item.goalDate?.toString())")
    //println("")
}

if sqlite3_step(statement) != SQLITE_DONE {
    println("error updateing table")
    sqlite3_close(database)
    return
}
sqlite3_finalize(statement)

sqlite3_close(database)

Referenced from SQLITE_TRANSIENT undefined in Swift