iPhone作为Xcode模拟器无法从sqlite数据库读取 [英] iPhone as Xcode simulator doesn't read from sqlite database

查看:54
本文介绍了iPhone作为Xcode模拟器无法从sqlite数据库读取的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了一个利用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)编写防止将来发生这种情况的代码.

因此,我建议:

  1. 从相关设备中删除您的应用.这将删除在此开发/测试过程中创建的所有空白数据库.

  2. 如果要使用预填充的数据库分发应用程序,请仅使用SQLITE_OPEN_READWRITE选项删除所有对sqlite3_open的引用,并用sqlite3_open_v2替换它们(确保该应用程序无法为该数据库创建空白数据库)你).具体来说,不要使用SQLITE_OPEN_CREATE选项.

  3. 重新访问您的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
    }
    

    在上面,有几件事需要引起您的注意:

    • 我建议您使用应用程序支持目录,而不要使用documents文件夹.参见 iOS存储最佳做法视频.

    • 只需尝试在应用程序支持文件夹中打开数据库.如果失败,请尝试从分发包复制到应用程序支持目录,然后重试.

    • 顺便说一句,如果sqlite3_open_v2(或sqlite3_open)失败,请记住您仍然必须调用sqlite3_close.正如 SQLite文档所说,打开时是否发生错误,当不再需要与数据库连接句柄关联的资源时,应将其传递给sqlite3_close()来释放它."

  4. 现在您的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:

  1. Remove your app from the device in question. This will remove any blank databases created during this development/testing process.

  2. If you are distributing app with prepopulated database, remove all references to sqlite3_open and replace them with sqlite3_open_v2, using only the SQLITE_OPEN_READWRITE option (making sure that the app cannot possibly create a blank database for you). Specifically, do not use the SQLITE_OPEN_CREATE option.

  3. 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 (or sqlite3_open) fails, remember that you still have to call sqlite3_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 to sqlite3_close() when it is no longer required."

  4. Now your readGenderCode can use this open 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆