RLMException"路径上的领域' '已经打开了不同的加密密钥"在writeCopy之后(toFile:,encryptionKey :)

时间:2017-08-20 09:56:32

标签: ios swift3 realm


我正尝试使用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:)没有更改encryptionKey,或者Realm仍然看到旧的.realm文件。但有趣的是,重新打开我的应用后,loadAll(withEncryptionKey:)会使用新的加密密钥加载数据而不会出现任何问题。
如何解决这个问题呢?如何更改加密密钥仍然可以使用应用程序?
我非常感谢你的帮助。

1 个答案:

答案 0 :(得分:1)

这取决于您调用loadAll()方法的位置。也许你在completion()打电话,对吗?如果是这样的话,那时对旧王国的引用尚未公布,它仍然是开放的。

就像从磁盘删除/替换Realm文件一样,如果您的应用程序当前没有打开Realm文件,那么替换磁盘上的Realm文件是唯一安全的。

来自Realm的Deleting Realm files文档:

  

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

     

实际上,这意味着删除Realm文件应该在应用程序启动之前在打开Realm之前完成,或者仅在显式自动释放池中打开Realm之后完成,这样可以确保所有Realm对象都已经存在解除分配。

原因是Realm维护了打开文件的内存缓存,因此尝试打开已经打开的文件将导致对已经打开的文件的引用。此打开文件将继续引用磁盘上的原始文件,即使它已被替换。确保已清除对Realm访问者对象的所有引用意味着Realm将不会返回现有的打开文件,而是从磁盘中打开该文件。

换句话说,您必须确保没有引用Realm的访问者对象(RealmResultsThreadSafeReferenceObject实例)您尝试替换Realm文件时的要点。您还必须确保您所做的任何引用已被取消分配。

如果此时没有其他对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的访问者对象,否则您将获得新旧数据的奇怪组合,但它不会那么重要确保取消分配访问者对象。