iPhone作为Xcode模拟器无法从sqlite数据库读取 [英] iPhone as Xcode simulator doesn't read from sqlite database
问题描述
我编写了一个利用Xcode附带的sqlite数据库的应用程序.它在Mac上运行良好,但是当我选择iPhone作为模拟器时,该应用程序(在iPhone上)无法读取数据库数据.我是否需要对访问sqlite数据库的代码进行编码,使其不同于在Mac上对其访问的代码?
这是我的功能,用于从在选择器视图中使用的数据库中获取数据.当我使用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)))
}
}
这种行为(您看不到数据库中的数据)通常只是表示它为空.
因此,首先,确认这一论点.从物理设备下载容器(请参见 https://stackoverflow.com/a/38064225/1271826 )并打开从macOS在此容器中找到的数据库,并检查其中包含的内容.我打赌该数据库或表为空.
假设这确实是问题所在,那就引出一个问题,即您最终如何在该设备上获得空白数据库,而不是在应用程序捆绑包中复制数据库.最有可能的是,在物理设备上进行开发和测试的某个时候,该应用程序意外打开了documents文件夹中的数据库,而没有首先成功复制捆绑软件版本.不幸的是,sqlite3_open
的默认行为是如果找不到一个数据库,则创建一个空白数据库.因此,您想要(a)从设备中删除该空白数据库; (b)编写防止将来发生这种情况的代码.
因此,我建议:
-
从相关设备中删除您的应用.这将删除在此开发/测试过程中创建的所有空白数据库.
-
如果要使用预填充的数据库分发应用程序,请仅使用
SQLITE_OPEN_READWRITE
选项删除所有对sqlite3_open
的引用,并用sqlite3_open_v2
替换它们(确保该应用程序无法为该数据库创建空白数据库)你).具体来说,不不要使用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: $0, encoding: .utf8) } print(error ?? "unknown error") close() return false } return true } func close() { sqlite3_close(db) db = nil }
在上面,有几件事需要引起您的注意:
-
现在您的
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: $0) } 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调用不断地重新打开数据库,请记住也要关闭它. /p> -
如果准备"成功,请确保也将其完成",否则您将泄漏.
-
如果可以的话,我建议避免使用力解开运算符
!
.
-
I have coded an app that utilizes the sqlite database that ships with Xcode. It works well on the mac but when I select iPhone as simulator the app (on iPhone) doesn't read database data. Do I need to code access to the sqlite database differently from how it accesses it on the mac?
Here is my function to get data from a database that is used in a picker view. This picker view does not populate when I use an iPhone (linked to the computer via usb). However it populates when I run any of the other listed simulators
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)))
}
}
This behavior, where you’re not seeing the data in the database, generally just means that it is empty.
So, first, confirm this thesis. Download the container from the physical device (see https://stackoverflow.com/a/38064225/1271826) and open up the database found in this container from macOS and examine what it contains. I’m wagering that the database or the table is empty.
Assuming this is indeed the problem, it begs the question as to how you ended up with a blank database on that device rather than a copy of the database within your app bundle. Most likely, at some point during development and testing on the physical device, the app accidentally opened a database in the documents folder without first successfully copying the bundle version. Unfortunately, the default behavior of sqlite3_open
is to create a blank database if one is not found. So, you want to (a) remove that blank database from your device; and (b) write code that prevents this from being able to happen in the future.
I would therefore suggest:
Remove your app from the device in question. This will remove any blank databases created during this development/testing process.
If you are distributing app with prepopulated database, remove all references to
sqlite3_open
and replace them withsqlite3_open_v2
, using only theSQLITE_OPEN_READWRITE
option (making sure that the app cannot possibly create a blank database for you). Specifically, do not use theSQLITE_OPEN_CREATE
option.Revisit your
open
routine. E.g. you might do something like: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: $0, encoding: .utf8) } print(error ?? "unknown error") close() return false } return true } func close() { sqlite3_close(db) db = nil }
There are a few things that I’d like to draw your attention to in the above:
I would suggest you use the application support directory rather than the documents folder. See iOS Standard Directories: Where Files Reside or watch iOS Storage Best Practices video.
Just try to open database in the application support folder. If it fails, try copying from bundle to application support directory and try again.
By the way, if
sqlite3_open_v2
(orsqlite3_open
) fails, remember that you still have to callsqlite3_close
. As the SQLite documentation says, "Whether or not an error occurs when it is opened, resources associated with the database connection handle should be released by passing it tosqlite3_close()
when it is no longer required."
Now your
readGenderCode
can use thisopen
routine: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: $0) } 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 }
Note:
If you’re going to open database when running SQL, you’ll want to close it when you’re done. Personally, I open database once and leave it open (rather than littering
open
calls throughout my database controller), but if you’re going to constantly re-open the database for every SQL call, remember to close it, too.If your "prepare" succeeded, make sure to "finalize" it, too, or else you will leak.
I’d suggest avoiding force unwrapping operator,
!
, if you can.
这篇关于iPhone作为Xcode模拟器无法从sqlite数据库读取的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!