iPhone 因为 Xcode 模拟器不从 sqlite 数据库读取
iPhone as Xcode simulator doesn't read from sqlite database
我编写了一个使用 Xcode 附带的 sqlite 数据库的应用程序。它在 mac 上运行良好,但是当我 select iPhone 作为模拟器时,应用程序(在 iPhone 上)不读取数据库数据。我是否需要以不同于在 mac 上访问它的方式对 sqlite 数据库进行编码访问?
这是我的函数,用于从选择器视图中使用的数据库中获取数据。当我使用 iPhone(通过 USB 连接到计算机)时,此选择器视图不会填充。但是,当我 运行 任何其他列出的模拟器
时它会填充
struct Gender {
let gender:String
}
var genderCode = [Gender]()
func readGenderCode(){
//database setup
let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) .appendingPathComponent("OTC.sqlite")
//opening the OTC database
if sqlite3_open(fileURL.path, &db) != SQLITE_OK {
print("error opening database")
}
//get data by query
let queryString = "SELECT gender from gender"
//statement pointer
var stmt2:OpaquePointer?
//preparing the query
if sqlite3_prepare(db, queryString, -1, &stmt2, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error preparing read: \(errmsg)")
return
}
//go through gender table records
while(sqlite3_step(stmt2) == SQLITE_ROW) {
let gc = String(cString: sqlite3_column_text(stmt2,0))
//populate the array
genderCode.append(Gender(gender: String(describing: gc)))
}
}
这种你看不到数据库中数据的行为,通常只意味着它是空的。
所以,首先,确认这个论点。从物理设备下载容器(参见 )并从 macOS 打开在此容器中找到的数据库并检查其中包含的内容。我打赌数据库或 table 是空的。
假设这确实是问题所在,它会引出一个问题,即您如何在该设备上得到一个空白数据库,而不是应用程序包中的数据库副本。最有可能的是,在物理设备上进行开发和测试期间,应用程序意外打开了文档文件夹中的数据库,而没有先成功复制捆绑版本。不幸的是,sqlite3_open
的默认行为是在找不到数据库时创建一个空白数据库。因此,您想 (a) 从您的设备中删除该空白数据库; (b) 编写代码以防止这种情况在未来发生。
因此我建议:
从有问题的设备上删除您的应用程序。这将删除在此 development/testing 过程中创建的所有空白数据库。
如果您使用预先填充的数据库分发应用程序,请删除对 sqlite3_open
的所有引用并将其替换为 sqlite3_open_v2
,仅使用 SQLITE_OPEN_READWRITE
选项(确保该应用程序不可能为您创建一个空白数据库)。具体来说,不要使用SQLITE_OPEN_CREATE
选项。
重温您的 open
例程。例如。你可以这样做:
func open() -> Bool {
let fileUrl = try! FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(databaseName)
if sqlite3_open_v2(fileUrl.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK {
return true
}
close() // even though open failed, you still need to close it
guard let bundleURL = Bundle.main.url(forResource: databaseName, withExtension: nil) else {
print("not found in bundle")
return false
}
try? FileManager.default.copyItem(at: bundleURL, to: fileUrl)
guard sqlite3_open_v2(fileUrl.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK else {
let error = sqlite3_errmsg(db).flatMap { String(cString: [=10=], encoding: .utf8) }
print(error ?? "unknown error")
close()
return false
}
return true
}
func close() {
sqlite3_close(db)
db = nil
}
以上有几点想提请大家注意:
我建议您使用应用程序支持目录而不是文档文件夹。请参阅 iOS Standard Directories: Where Files Reside or watch iOS Storage Best Practices 视频。
只需尝试在应用程序支持文件夹中打开数据库。如果失败,请尝试从捆绑包复制到应用程序支持目录,然后重试。
顺便说一句,如果sqlite3_open_v2
(或sqlite3_open
)失败了,记住你还是要调用sqlite3_close
。正如 SQLite documentation 所说,“无论打开时是否发生错误,与数据库连接句柄关联的资源都应在不再需要时通过将其传递给 sqlite3_close()
来释放。”
现在您的 readGenderCode
可以使用这个 open
例程:
func readGenderCode() -> [Gender]? {
guard open() else { return nil }
defer { close() }
let sql = "SELECT gender from gender"
var statement: OpaquePointer?
guard sqlite3_prepare(db, sql, -1, &statement, nil) == SQLITE_OK else {
let errmsg = sqlite3_errmsg(db).flatMap { String(cString: [=11=]) }
print("error preparing read:", errmsg ?? "Unknown error")
return nil
}
defer { sqlite3_finalize(statement) }
var genders = [Gender]()
//go through gender table records
while sqlite3_step(statement) == SQLITE_ROW {
if let cString = sqlite3_column_text(statement, 0) {
let string = String(cString: cString)
genders.append(Gender(gender: string))
}
}
return genders
}
注:
如果您要在 运行 SQL 时打开数据库,您需要在完成后关闭它。就个人而言,我打开数据库一次并保持打开状态(而不是在我的数据库控制器中乱扔 open
调用),但是如果您要为每个 SQL 调用不断重新打开数据库,请记住也关闭它。
如果你的“准备”成功了,一定要“完成”它,否则你会泄漏。
如果可以,我建议避免使用强制展开运算符 !
。
我编写了一个使用 Xcode 附带的 sqlite 数据库的应用程序。它在 mac 上运行良好,但是当我 select iPhone 作为模拟器时,应用程序(在 iPhone 上)不读取数据库数据。我是否需要以不同于在 mac 上访问它的方式对 sqlite 数据库进行编码访问?
这是我的函数,用于从选择器视图中使用的数据库中获取数据。当我使用 iPhone(通过 USB 连接到计算机)时,此选择器视图不会填充。但是,当我 运行 任何其他列出的模拟器
时它会填充struct Gender {
let gender:String
}
var genderCode = [Gender]()
func readGenderCode(){
//database setup
let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) .appendingPathComponent("OTC.sqlite")
//opening the OTC database
if sqlite3_open(fileURL.path, &db) != SQLITE_OK {
print("error opening database")
}
//get data by query
let queryString = "SELECT gender from gender"
//statement pointer
var stmt2:OpaquePointer?
//preparing the query
if sqlite3_prepare(db, queryString, -1, &stmt2, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error preparing read: \(errmsg)")
return
}
//go through gender table records
while(sqlite3_step(stmt2) == SQLITE_ROW) {
let gc = String(cString: sqlite3_column_text(stmt2,0))
//populate the array
genderCode.append(Gender(gender: String(describing: gc)))
}
}
这种你看不到数据库中数据的行为,通常只意味着它是空的。
所以,首先,确认这个论点。从物理设备下载容器(参见
假设这确实是问题所在,它会引出一个问题,即您如何在该设备上得到一个空白数据库,而不是应用程序包中的数据库副本。最有可能的是,在物理设备上进行开发和测试期间,应用程序意外打开了文档文件夹中的数据库,而没有先成功复制捆绑版本。不幸的是,sqlite3_open
的默认行为是在找不到数据库时创建一个空白数据库。因此,您想 (a) 从您的设备中删除该空白数据库; (b) 编写代码以防止这种情况在未来发生。
因此我建议:
从有问题的设备上删除您的应用程序。这将删除在此 development/testing 过程中创建的所有空白数据库。
如果您使用预先填充的数据库分发应用程序,请删除对
sqlite3_open
的所有引用并将其替换为sqlite3_open_v2
,仅使用SQLITE_OPEN_READWRITE
选项(确保该应用程序不可能为您创建一个空白数据库)。具体来说,不要使用SQLITE_OPEN_CREATE
选项。重温您的
open
例程。例如。你可以这样做:func open() -> Bool { let fileUrl = try! FileManager.default .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) .appendingPathComponent(databaseName) if sqlite3_open_v2(fileUrl.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK { return true } close() // even though open failed, you still need to close it guard let bundleURL = Bundle.main.url(forResource: databaseName, withExtension: nil) else { print("not found in bundle") return false } try? FileManager.default.copyItem(at: bundleURL, to: fileUrl) guard sqlite3_open_v2(fileUrl.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK else { let error = sqlite3_errmsg(db).flatMap { String(cString: [=10=], encoding: .utf8) } print(error ?? "unknown error") close() return false } return true } func close() { sqlite3_close(db) db = nil }
以上有几点想提请大家注意:
我建议您使用应用程序支持目录而不是文档文件夹。请参阅 iOS Standard Directories: Where Files Reside or watch iOS Storage Best Practices 视频。
只需尝试在应用程序支持文件夹中打开数据库。如果失败,请尝试从捆绑包复制到应用程序支持目录,然后重试。
顺便说一句,如果
sqlite3_open_v2
(或sqlite3_open
)失败了,记住你还是要调用sqlite3_close
。正如 SQLite documentation 所说,“无论打开时是否发生错误,与数据库连接句柄关联的资源都应在不再需要时通过将其传递给sqlite3_close()
来释放。”
现在您的
readGenderCode
可以使用这个open
例程:func readGenderCode() -> [Gender]? { guard open() else { return nil } defer { close() } let sql = "SELECT gender from gender" var statement: OpaquePointer? guard sqlite3_prepare(db, sql, -1, &statement, nil) == SQLITE_OK else { let errmsg = sqlite3_errmsg(db).flatMap { String(cString: [=11=]) } print("error preparing read:", errmsg ?? "Unknown error") return nil } defer { sqlite3_finalize(statement) } var genders = [Gender]() //go through gender table records while sqlite3_step(statement) == SQLITE_ROW { if let cString = sqlite3_column_text(statement, 0) { let string = String(cString: cString) genders.append(Gender(gender: string)) } } return genders }
注:
如果您要在 运行 SQL 时打开数据库,您需要在完成后关闭它。就个人而言,我打开数据库一次并保持打开状态(而不是在我的数据库控制器中乱扔
open
调用),但是如果您要为每个 SQL 调用不断重新打开数据库,请记住也关闭它。如果你的“准备”成功了,一定要“完成”它,否则你会泄漏。
如果可以,我建议避免使用强制展开运算符
!
。