Swift GRDB 写入数据库错误 "attempt to write a readonly database"(尝试写入只读数据库)

3

我正在尝试写入我创建的语录数据库。如果用户选择“收藏”按钮,它将在SQLITE数据库的“favorite”列中添加1(或true)值。但由于某些原因,我无法向数据库写入数据,执行以下代码时会出现问题:

    @IBAction func addFavorite(_ sender: Any) {
        var configuration = Configuration()
        configuration.readonly = false
        let dbPath = Bundle.main.path(forResource: "data", ofType: "db")!
        let dbQueue = try! DatabaseQueue(path: dbPath, configuration: configuration)

        try! dbQueue.inDatabase { db in
            try db.execute("""
UPDATE quotes SET favorite = 1 WHERE quote = ?;
""",
                                           arguments: [quoteLabel.text])
    }

我希望结果能够写入数据库并将 1 放置在收藏列中,但可悲的是事实并非如此。我收到了这个错误:
Thread 1: 致命错误:'try!'表达式意外地引发了错误:SQLite 错误 8,语句为UPDATE quotes SET favorite = 1 WHERE quote = ?参数为["Abstinence is the great strengthener and clearer of reason."] :尝试写入只读数据库
我尝试过使用 chmod 755 更改数据库权限,但也无济于事。我是否遗漏了什么?
1个回答

6

您的数据库路径是应用程序资源的路径。在iOS中,应用程序资源是不可修改的。

有关更多信息,请参见文件系统基础知识。特别相关的引用如下:

[...] 应用程序包。该目录包含应用程序及其所有资源。

您无法写入此目录。为防止篡改,在安装时签署了捆绑目录。写入此目录会更改签名并防止应用程序启动。但是,您可以获得只读访问权限以访问存储在应用程序包中的任何资源。有关更多信息,请参见资源编程指南

现在,如果不能修改它,您如何写入数据库?

好吧,您需要将其复制到可以修改的位置。您永远不会修改捆绑的数据库资源,只会修改副本。

这里再次提供文档:如何打开存储为应用程序资源的数据库? GRDB FAQ中提到:

If the application should modify the database resource, you need to copy it to a place where it can be modified. For example, in the Application Support directory. Only then, open a connection:

let fileManager = FileManager.default

let dbPath = try fileManager
    .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    .appendingPathComponent("db.sqlite")
    .path

if !fileManager.fileExists(atPath: dbPath) {
    let dbResourcePath = Bundle.main.path(forResource: "db", ofType: "sqlite")!
    try fileManager.copyItem(atPath: dbResourcePath, toPath: dbPath)
}

let dbQueue = try DatabaseQueue(path: dbPath)

感谢您提供这段代码。一旦数据库被修改,是否可以将更改写入主包中?或者在尝试读取数据库以获取新添加的数据时,我是否必须使用相同的过程? - Kevin Maldjian
不行,因为你永远无法将更改复制到主捆绑包中,因为资源是永远不能被修改的。但请仔细阅读示例代码并尝试理解它:它会执行以下两个步骤:1. 仅在复制不存在时才复制资源数据库,然后2. 打开已复制的可写数据库。这意味着,在独特的初始复制之后执行的更改将在每次启动后保留:应用程序不会使用资源数据库的副本覆盖更改。 - Gwendal Roué
最后一条评论:由于应用程序捆绑的数据库副本仅发生一次,因此当您的应用程序最终更新时,人们可能会想知道会发生什么。当先前版本已经被复制时,新应用程序版本附带的更新资源数据库将不会被使用。我不知道这是否可以接受。这取决于您的特定应用程序。如果不行,那么您将不得不设计一种策略,以允许本地修改和来自未来数据库版本的更新。这更加不容易,是完全另一个问题。 - Gwendal Roué
仅将东西保存到核心数据是明智的吗?要保存的数据是一组收藏的引用。 - Kevin Maldjian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接