RLMException“路径''的领域已使用不同的加密密钥打开"在writeCopy(toFile:,encryptionKey :)之后 [英] RLMException "Realm at path ' ' already opened with different encryption key" after writeCopy(toFile:,encryptionKey:)

查看:60
本文介绍了RLMException“路径''的领域已使用不同的加密密钥打开"在writeCopy(toFile:,encryptionKey :)之后的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


我正在尝试使用writeCopy(toFile:, encryptionKey:)更改Realm数据库的加密密钥,如下所示:

public static func updateEncryption(forNewKey newKey: String, withOldKey oldKey: String, completion: (() -> Void)) {

    let defaultURL = Backup.realmContainerURL
    let defaultParentURL = defaultURL.deletingLastPathComponent()
    let compactedURL = defaultParentURL.appendingPathComponent("default-compact.realm")

    let oldKeyData = oldKey.pbkdf2SHA256(keyByteCount: 64)
    let newKeyData = newKey.pbkdf2SHA256(keyByteCount: 64)

    let oldEncryptionConfig = Realm.Configuration(fileURL: Backup.realmContainerURL, encryptionKey: oldKeyData)

    autoreleasepool {
        do {
            let oldRealm = try Realm(configuration: oldEncryptionConfig)
            try oldRealm.writeCopy(toFile: compactedURL, encryptionKey: newKeyData)
            oldRealm.invalidate()
            try FileManager.default.removeItem(at: defaultURL)
            try FileManager.default.moveItem(at: compactedURL, to: defaultURL)
            completion()
        } catch {
            fatalError(error.localizedDescription)
        }
    }
}

之后,我将使用以下命令在应用程序中重新加载数据:

public static func loadAll(withEncryptionKey encryptionKey: String) -> [Backup] {
    do {
        let key = encryptionKey.pbkdf2SHA256(keyByteCount: 64)
        let encryptionConfig = Realm.Configuration(fileURL: Backup.realmContainerURL, encryptionKey: key)
        let realm = try Realm(configuration: encryptionConfig)
        let allObjects = realm.objects(Backup.self).sorted(byKeyPath: "date", ascending: false)
        return allObjects.map({ $0 })
    } catch {
        fatalError(error.localizedDescription)
    }
}

因此,我将loadAll(withEncryptionKey:)函数与新键一起使用,但是在let realm = try Realm(configuration: encryptionConfig)应用程序崩溃时,RLMRealm.mm文件的第347行:@throw RLMException(@"Realm at path '%s' already opened with different encryption key", config.path.c_str());带有控制台日志libc++abi.dylib: terminating with uncaught exception of type NSException.
因此,看来writeCopy(toFile:, encryptionKey:)并没有更改加密密钥,或者Realm仍然看到旧的.realm文件.但是有趣的是,重新打开我的应用程序后,loadAll(withEncryptionKey:)使用新的加密密钥加载数据没有任何问题.
如何解决这个问题呢?如何更改加密密钥并仍然能够使用应用程序?
非常感谢您的帮助.

解决方案

这取决于调用loadAll()方法的位置.也许您正在呼叫completion(),对吗?如果是这样,那时候尚未释放对旧Realm的引用,并且它仍然是开放的.

就像从磁盘上删除/替换Realm文件一样,只有在您的应用程序当前未打开Realm文件的情况下,替换磁盘上的Realm文件才是安全的.

摘自Realm上删除Realm文件的文档::

因为Realm避免将数据复制到内存中,除非绝对需要,否则Realm管理的所有对象都包含对磁盘上文件的引用,并且必须先对其进行释放,然后才能安全删除该文件.这包括从领域读取(或添加到)的所有对象,所有ListResultsThreadSafeReference对象以及Realm本身.

实际上,这意味着删除Realm文件应该在打开Realm之前在应用程序启动时完成,或者仅在显式自动释放池中打开Realm之后才执行,以确保所有Realm对象都已被删除.释放.

之所以这样做,是因为Realm维护了打开文件的内存缓存,因此,尝试打开已打开的文件将导致返回对已打开文件的引用.该打开的文件将继续引用磁盘上的原始文件,即使此文件已被替换.确保已清除对Realm访问器对象的所有引用,这意味着Realm将没有要返回的现有打开文件,而是从磁盘打开该文件.

换句话说,在尝试替换Realm文件时,必须确保没有对Realm的访问器对象(RealmResultsThreadSafeReferenceObject实例)的引用.此后,还必须确保您 did 所引用的所有内容都已被释放.

如果此时没有对Realm和Realm对象的其他引用,则可以通过在autorelease块之外打开来成功,如下所示.

autoreleasepool {
    do {
        let oldRealm = try Realm(configuration: oldEncryptionConfig)
        try oldRealm.writeCopy(toFile: compactedURL, encryptionKey: newKeyData)
        oldRealm.invalidate()
        try FileManager.default.removeItem(at: defaultURL)
        try FileManager.default.moveItem(at: compactedURL, to: defaultURL)
    } catch {
        fatalError(error.localizedDescription)
    }
}

loadAll(withEncryptionKey: ...)

一种可能更易于管理的替代方法是,当您尝试重新打开还原的文件时,使用不同的路径.由于您要访问磁盘上的其他路径,因此可以保证打开新文件.您仍然需要确保您没有对Realm的访问器对象的引用,因为否则您会得到新旧数据的奇怪混合,但是确保确保访问器对象已被释放并不重要./p>


I'm trying to change encryption key for my Realm database using writeCopy(toFile:, encryptionKey:), like below:

public static func updateEncryption(forNewKey newKey: String, withOldKey oldKey: String, completion: (() -> Void)) {

    let defaultURL = Backup.realmContainerURL
    let defaultParentURL = defaultURL.deletingLastPathComponent()
    let compactedURL = defaultParentURL.appendingPathComponent("default-compact.realm")

    let oldKeyData = oldKey.pbkdf2SHA256(keyByteCount: 64)
    let newKeyData = newKey.pbkdf2SHA256(keyByteCount: 64)

    let oldEncryptionConfig = Realm.Configuration(fileURL: Backup.realmContainerURL, encryptionKey: oldKeyData)

    autoreleasepool {
        do {
            let oldRealm = try Realm(configuration: oldEncryptionConfig)
            try oldRealm.writeCopy(toFile: compactedURL, encryptionKey: newKeyData)
            oldRealm.invalidate()
            try FileManager.default.removeItem(at: defaultURL)
            try FileManager.default.moveItem(at: compactedURL, to: defaultURL)
            completion()
        } catch {
            fatalError(error.localizedDescription)
        }
    }
}

After that, I'm reloading data in my app using:

public static func loadAll(withEncryptionKey encryptionKey: String) -> [Backup] {
    do {
        let key = encryptionKey.pbkdf2SHA256(keyByteCount: 64)
        let encryptionConfig = Realm.Configuration(fileURL: Backup.realmContainerURL, encryptionKey: key)
        let realm = try Realm(configuration: encryptionConfig)
        let allObjects = realm.objects(Backup.self).sorted(byKeyPath: "date", ascending: false)
        return allObjects.map({ $0 })
    } catch {
        fatalError(error.localizedDescription)
    }
}

So I use loadAll(withEncryptionKey:) function with a new key, but on let realm = try Realm(configuration: encryptionConfig) app crashes in RLMRealm.mm file, line 347: @throw RLMException(@"Realm at path '%s' already opened with different encryption key", config.path.c_str()); with console log libc++abi.dylib: terminating with uncaught exception of type NSException.
So it looks like writeCopy(toFile:, encryptionKey:) did not change encryptionKey, or Realm still sees the old .realm file. But what's funny, after reopening my app, loadAll(withEncryptionKey:) loads data with new encryption key without any problems.
How to solve this problem? How to change encryption key and still be able to use app?
I would be very grateful for your help.

解决方案

It depends on where you call the loadAll() method. Perhaps you are calling in completion(), right? If so, at that point the reference to the old Realm has not yet been released and it remains open.

Much like deleting/replacing a Realm file from disk, it's only safe to replace a Realm file on disk if your application does not currently have the Realm file open.

From Realm's documentation on Deleting Realm files:

Because Realm avoids copying data into memory except when absolutely required, all objects managed by a Realm contain references to the file on disk, and must be deallocated before the file can be safely deleted. This includes all objects read from (or added to) the Realm, all List, Results, and ThreadSafeReference objects, and the Realm itself.

In practice, this means that deleting a Realm file should be done either on application startup before you have opened the Realm, or after only opening the Realm within an explicit autorelease pool, which ensures that all of the Realm objects will have been deallocated.

The reason for this is that Realm maintains an in-memory cache of open files, so attempting to open a file that's already open will result in a reference to the already-open file being returned. This open file will continue to refer to the original file on disk, even if it has since been replaced. Ensuring that all references to Realm accessor objects have been cleaned up means that Realm will not have an existing open file to return, and will instead open the file from disk.

In other words, you must make sure you have no references to Realm's accessor objects (Realm, Results, ThreadSafeReference or Object instances) at the point when you attempt to replace the Realm file. You must also make sure that any references you did have have since been deallocated.

If there are no other references to Realm and Realm objects at that point, it will succeed by opening outside the autorelease block, like the following.

autoreleasepool {
    do {
        let oldRealm = try Realm(configuration: oldEncryptionConfig)
        try oldRealm.writeCopy(toFile: compactedURL, encryptionKey: newKeyData)
        oldRealm.invalidate()
        try FileManager.default.removeItem(at: defaultURL)
        try FileManager.default.moveItem(at: compactedURL, to: defaultURL)
    } catch {
        fatalError(error.localizedDescription)
    }
}

loadAll(withEncryptionKey: ...)

An alternative approach that may be easier to manage is to use a different path when you attempt to reopen the restored file. Since you're accessing a different path on disk you'll be guaranteed to open the new file. You'll still need to make sure you have no references to Realm's accessor objects since otherwise you'll get a weird mix of old and new data, but it won't be as critical that you ensure that the accessor objects were deallocated.

这篇关于RLMException“路径''的领域已使用不同的加密密钥打开"在writeCopy(toFile:,encryptionKey :)之后的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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