【问题标题】:New Realm migration gets called on new app installs新应用安装时调用新领域迁移
【发布时间】:2020-01-19 15:29:46
【问题描述】:

我有一个使用 Realm 的应用程序,它目前有两个不同的迁移,我正在处理第三个迁移,但由于某种原因,这个新迁移的行为很奇怪。我理解迁移的方式是,只有在以前的架构版本已经存在时,新的迁移才会运行,否则它们将被忽略,架构将基于模型的结构创建。

这就是我所看到的。

场景 1:工作正常。

如果之前安装了该应用程序,一切正常,新的迁移 3 运行,它完成了它应该做的一切,数据成功转换,一切正常。

场景 2:错误。

如果是第一次安装应用,用户一启动就开始添加数据,下一次启动应用就会崩溃(第二次调用 AppDelegate)。我放了一条打印语句,看起来迁移 3 在应用程序第二次启动时运行,但不是在第一次启动时运行。

场景 3:破解后工作正常。

如果应用是第一次安装,但这次是用户 不会立即输入任何数据,而是将应用程序杀死,然后再次重新启动应用程序,一切正常,没有错误,可以毫无问题地输入数据。

换句话说,migration 3 似乎在应用程序第二次启动时尝试运行,此时发生错误,因为数据库在第一次运行时已经具有正确的架构结构。

错误场景 2:

libc++abi.dylib:以 NSException 类型的未捕获异常终止

知道为什么迁移 3 会在新安装的第二次启动时运行吗?这似乎不是正常行为,为什么它会运行第二次而不是第一次启动。

有什么想法吗?

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

            /// Migration 1:
            Realm.Configuration.defaultConfiguration = Realm.Configuration(
                schemaVersion: 1,
                migrationBlock: { migration, oldSchemaVersion in
                    if (oldSchemaVersion < 1) {
                        // configuration for schema 1
                    }
            })        
            /// Migration 2:
            Realm.Configuration.defaultConfiguration = Realm.Configuration(
                schemaVersion: 2,
                migrationBlock: { migration, oldSchemaVersion in
                    if (oldSchemaVersion < 2) {
                        // configuration for schema 2
                    }
            })
            /// Migration 3:
            Realm.Configuration.defaultConfiguration = Realm.Configuration(
                schemaVersion: 3,
                migrationBlock: { migration, oldSchemaVersion in
                    if (oldSchemaVersion < 3) {
                        // configuration for schema 3
                    }
            })
        }
    }

仅供参考 - 我在每次迁移中都有打印,并且在第一次安装应用程序后的第二次应用程序启动时仅运行迁移 3。

【问题讨论】:

  • 我相信你的意思是 migration block 运行,而不是模式。 Schema 描述了数据的结构并且不运行。当应用程序第一次运行时,没有这样的 schemaVersion,因此将根据它们所处的任何状态创建默认对象。但是,由于问题中提供的代码序列,在第一次运行时,架构版本最终会是 3。这是故意的吗?
  • @Jay - 是的,你是对的,我的意思是迁移,我已经纠正了我的问题。我的应用程序目前已通过第二次迁移发布。我现在添加了第三次迁移,以便能够将Double 转换为双精度列表List&lt;Double&gt;,因此我在第三次迁移中处理数据转换。我只希望为已经安装应用程序的用户而不是新用户运行第三次迁移,根据我上面显示的迁移代码,这个假设是否正确?谢谢
  • @Jay - 你介意为我澄清以下内容吗?根据我展示的迁移顺序,迁移 3 是否会为新用户运行?我的意图是为已有架构 1 或 2 的现有用户运行迁移 3,但不为新用户运行。
  • 不,不会发生迁移,因为新用户以前不存在该领域。所有对象都将是新鲜的,无需迁移 - 请参阅我的第一条评论。但是,模式版本将设置为 3。您可以通过在闭包中添加一些打印语句来测试它。删除现有领域并运行它。
  • 由于oldSchemaVersion &lt; 3,如果架构版本为 3,则无法运行迁移 3 中的实际迁移。这告诉我在您的问题中的代码之外还有其他事情发生,因为它可以运行的唯一方法是 oldSchemaVersion 为

标签: ios swift realm database-migration realm-migration


【解决方案1】:

我将首先声明这是作为答案发布的,因为它试图验证问题中的代码在 OP 环境之外是否正常运行。

我已经获取了代码并在整个过程中添加了打印语句,因此可以在控制台中轻松地跟踪代码流。

我们从删除所有文件开始,以便模拟新安装的应用程序。您的评论说您输入了数据,但这不是全新安装,因为用户还没有任何数据。

这是添加了一些打印语句的重复代码。对我来说,这是一个 macOS 应用程序,但 iOS 应用程序的行为是一样的。

迁移函数位于AppDelegate中,这样调用

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        self.doMigrate()

然后是函数

func doMigrate() {

    print("starting schema version: \(Realm.Configuration.defaultConfiguration.schemaVersion)")

    /// Migration 1:
    Realm.Configuration.defaultConfiguration = Realm.Configuration(
        schemaVersion: 1,
        migrationBlock: { migration, oldSchemaVersion in
            print("migration 1")
            if (oldSchemaVersion < 1) {
                print(" old schema < 1, performaing migration")
                // configuration for schema 1
            }
    })

    print("schema is now: \(Realm.Configuration.defaultConfiguration.schemaVersion)")

    /// Migration 2:
    Realm.Configuration.defaultConfiguration = Realm.Configuration(
        schemaVersion: 2,
        migrationBlock: { migration, oldSchemaVersion in
            print("migration 2")
            if (oldSchemaVersion < 2) {
                print(" old schema < 2, performaing migration")
                // configuration for schema 2
            }
    })

    print("schema is now: \(Realm.Configuration.defaultConfiguration.schemaVersion)")

    /// Migration 3:
    Realm.Configuration.defaultConfiguration = Realm.Configuration(
        schemaVersion: 3,
        migrationBlock: { migration, oldSchemaVersion in
            print("migration 3")
            if (oldSchemaVersion < 3) {
                print(" old schema < 3, performaing migration")
                // configuration for schema 3
            }
    })

    print("schema is now: \(Realm.Configuration.defaultConfiguration.schemaVersion)")
}

第一次运行时,这里是输出,注意没有发生迁移

starting schema version: 0
schema is now: 1
schema is now: 2
schema is now: 3

当代码第二次运行时,输出是相同的

starting schema version: 0
schema is now: 1
schema is now: 2
schema is now: 3

即使我添加或更改模型,添加或更改数据,输出仍然保持不变 - 除了它确实会崩溃,因为如果对象属性被删除,它需要迁移。

所以答案是:如果您没有看到相同的行为,则说明您的环境中的代码/领域结构有其他影响。无论运行一次还是 3 次,其工作方式都是相同的。

【讨论】:

  • 非常感谢您的关注。尽管这不是一个直接的答案,但我会将其标记为正确答案,因为它为我指明了正确的方向。正如您之前提到的,这可能与我在我的应用程序中所做的其他事情有关,我会深入研究它。非常感谢您验证我的代码。
  • @fs_tigre 酷。请让我了解情况,一旦发现问题的真正原因,请随时更新/编辑我的答案。
  • @ Jay - 我找到了在第二次运行中触发迁移 3 调用的原因。在我的应用程序的第一个ViewController 中,我正在读取ViewDidLoad 方法中的所有realm 对象,这是触发它的原因。我不太明白的是,这怎么会触发AppDelegate 类中的块。事实上,在第一次运行之后,我可以随时点击按钮触发迁移 3 块,这对我来说没有意义,怎么可能调用 AppDelegate 内部的迁移块轻按一下按钮?阅读查询:realm.objects(Item.self)
  • @fs_tigre 这是 macOS 应用还是 iOS?
  • @fs_tigre 因此,第一次从代码中的任何位置访问 Realm 都会触发迁移块,无论它在哪里。换句话说,如果您从 viewDidLoad 中删除对领域的访问并将其放在按钮中,则迁移代码将不会执行。只要您单击该按钮,然后就会触发迁移。这就是关于闭包的事情——它们异步运行并且可以在以后“激活”。在我的应用程序委托中,我所做的是在迁移设置后立即添加一个“假”调用到领域以让那些触发let _ = try! Realm() //will fire the migration
猜你喜欢
  • 1970-01-01
  • 2017-06-03
  • 2018-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-28
  • 1970-01-01
相关资源
最近更新 更多