问题
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.
回答1:
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, andThreadSafeReferenceobjects, and theRealmitself.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.
来源:https://stackoverflow.com/questions/45780841/rlmexception-realm-at-path-already-opened-with-different-encryption-key-af