Realm migration memory leak iOS - ios

I am getting memory leak during migration using Realm (v1.0.2).
My code looks like that:
let config = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
migration.enumerate(MyClassRealm.className(), { (oldObject, newObject) in
newObject!["remoteId"] = 0
newObject!["deleted"] = false
newObject!["dirty"] = true
newObject!["updated"] = 0
})
}
})
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
do {
let _ = try Realm()
} catch {
TSLog.error(error as NSError)
}
Stack trace:
Do you have any idea what is wrong?

It's difficult to tell without symbols for the intermediate frames of the stack trace, but I suspect you're running into Realm Cocoa issue #2933, which appears to be a memory leak due to a bug in Swift's runtime libraries.

Related

In swift, is there a way to add new data from second Realm bundle, without overwriting any existing data in the current Default Realm?

Upon initial load of the app, the Bundled Realm (Realm1) is copied to the documents folder. Now that the bundled realm is set as the default realm, I am able update the bool property so that the table view can show marked and unmarked cells. However I am looking for a way to bundle a second realm (Realm2) with a later update, that will add new data to the existing default realm, but without overwriting the current default realm. I am currently working in swift 5 and Xcode 11.1, if that is helpful.
So far the only thing that I can think of is adding block of code to add new entries to the default realm. First the view will check to see what the count is of the realm, and if the count is the same as the original bundle, then it will add new data, if the count is equal to the initial bundle plus the new entries, then it will not add the new data again. I was hoping for a simpler solution that is cleaner in my opinion.
Ideally the end result would be a way to update the existing default realm, without overwriting the already edited content. Although I am rather new to using realm, any help in pointing me in the right direction for a solution would be greatly appreciated. Thanks.
Attached below is the current code I have implemented to load the default realm from the bundle.
let bundlePath = Bundle.main.path(forResource: "preloadedData", ofType: "realm")!
let defaultPath = Realm.Configuration.defaultConfiguration.fileURL!.path
let fileManager = FileManager.default
// Copy Realm on initial launch
if !fileManager.fileExists(atPath: defaultPath){
do {
try fileManager.copyItem(atPath: bundlePath, toPath: defaultPath)
print("Realm was copied")
} catch {
print("Realm was not coppied \(error)")
}
}
return true
Once you've created your default Realm on disk, if you want to read data from the bundled one, here's the code
let config = Realm.Configuration(
// Get the URL to the bundled file
fileURL: Bundle.main.url(forResource: "MyBundledData", withExtension: "realm"),
// Open the file in read-only mode as application bundles are not writeable
readOnly: true)
let realm = try! Realm(configuration: config)
and once you've read the data, you can switch back
var config = Realm.Configuration()
config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("\(some_realm_name).realm")
Realm.Configuration.defaultConfiguration = config
as far as not overwriting, ensure your objects use a unique primary key and when they are written, nothing will be overwritten as objects will a unique primary key will be added instead of overwriting.
class MyClass: Object {
#objc dynamic var my_primary_id = NSUUID().uuidString
I am adding an additional answer that's somewhat related to the first but also stands on it's own.
In a nutshell, once Realm connects to a data source, it will continue to use that data source as long as the objects are not released, even if the actual file is deleted.
The way around that is to encapsulate the Realm calls into an autorelease pool so that those objects can be released when the Realm is deleted.
Here’s an example:
This function adds a GameData object to the default.realm file.
func addAnObject() {
autoreleasepool {
let realm = try! Realm()
let testData = GameData()
testData.Scenario = "This is my scenario"
testData.Id = 1
try! realm.write {
realm.add(testData)
}
}
}
At this point, if you run the addAnObject code, your file will have a GameData object.
GameData {
Id = 1;
GameDate = (null);
Scenario = This is my scenario;
GameStarted = 0;
}
Then a function that delete’s the old realm, and copies the bundled realm to it’s place. This works because all of the interaction with Realm was enclosed in an autorelease pool so the objects can be released.
func createDefaultRealm() {
let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
let defaultParentURL = defaultURL.deletingLastPathComponent()
if let bundledRealmURL = self.bundleURL("default") {
do {
try FileManager.default.removeItem(at: defaultURL)
try FileManager.default.copyItem(at: bundledRealmURL, to: defaultURL)
} catch let error as NSError {
print(error.localizedDescription)
return
}
}
let migrationBlock : MigrationBlock = { migration, oldSchemaVersion in
//handle migration
}
Realm.Configuration.defaultConfiguration = Realm.Configuration(schemaVersion: 18, migrationBlock: migrationBlock)
print("Your default realm objects: \(try! Realm().objects(GameData.self))")
}
func bundleURL(_ name: String) -> URL? {
return Bundle.main.url(forResource: name, withExtension: "realm")
}
and please note that if you access Realm inside the class but outside an autorelease pool, Realm will refuse to 'let go' of it's objects.
Do do NOT do this!!
class ViewController: UIViewController {
var realm = try! Realm()

Realm configuration returns random schemaVersions, causing migration to fail

We are currently using Realm inside our app, but when I try to perform a migration (because we want to delete a Model/class in our database), the Configuration returns some random huge value from the configuration.schemaVersion.
The migration isn't called, and nothing is deleted. The Realm database was called multiple times with let realm = try? Realm(configuration: Realm.compactConfiguration)
I tried to make one Config throughout the app and set it like the following:
let configuration = Realm.compactingConfiguration
Realm.Configuration.defaultConfiguration = configuration
but the large schemaVersion still appears, and the code doesn't go inside the migrationBlock
extension Realm {
public static var compactConfiguration: Configuration {
get {
// Realm is compacted on the first open if the configuration block conditions were met.
// Compacting when size is greater than 50MB (arbitrary amount, database size should be
// around 2.6MB, so should not reach 50MB)
let currentSchemaVersion: UInt64 = 1
var configuration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < currentSchemaVersion) {
migration.deleteData(forType: Office.className())
}
})
configuration.deleteRealmIfMigrationNeeded = true
return configuration
}
}
}
Does anyone know what is going on? I expected the versionScheme to be 0 since it was never set before.

Realm: Update pre-populated Database while keeping data from a specific field

I got a pre-populated database for my app. Occasionally I'm going to be updating this pre-populated database with new data.
The problem is that the object contains a boolean field called "completed". By default this field is false. When the user completes a certain task the object's boolean field becomes true.
I want to find a way when migrating to the new pre-populated database to keep that boolean field the same as before.
Here is the code I have so far
// copy over old data files
let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
let defaultParentURL = defaultURL.deletingLastPathComponent()
if let v0URL = bundleURL("database") {
//Delete if there's a file already
do {
try FileManager.default.removeItem(at: defaultURL)
print("Item Removed")
} catch {
print("error removing seeds: \(error)")
}
//Copy the database file to the directory
do {
try FileManager.default.copyItem(at: v0URL, to: defaultURL)
print("Item Copied")
}catch {
print("error copying seeds: \(error)")
}
}
// define a migration block
// you can define this inline, but we will reuse this to migrate realm files from multiple versions
// to the most current version of our data model
let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in
migration.enumerateObjects(ofType: BookModel.className()) { oldObject, newObject in
// keep the old boolean completed value and assign it to the new one
let completed = oldObject!["completed"] as! Bool
newObject!["completed"] = completed
print(completed)
}
print("Migration complete.")
}
Realm.Configuration.defaultConfiguration = Realm.Configuration(schemaVersion: 5, migrationBlock: migrationBlock)
let realm = try! Realm()
return true
I am suspecting that my code does not work because before migration happens, I have already deleted my previous database (which contains the user's completed values). But how can I replace my previous database with the new one without delete-copy?
I can solve this with userDefaults but I would prefer the above solution.
Thank you in advance!

How to change attributes of a class without having to delete the app using realm

Currently I'm writing a program in Swift using realm. I'm fairly new to iOS development but my understanding of realm is when you change a class stored in realm, you need to delete the app from the device to get rid of the persist data. Unfortunately, I have manually entered in a pretty big database into the app.
Currently I need to change the name of an attribute within a class, but in the future may need to add attributes. What is the best way of updating realm storage so I don't need to delete the app?
Here is one of my models:
class Device: Object {
dynamic var name = ""
dynamic var id = ""
dynamic var os = ""
dynamic var currentUser: User?
dynamic var dateStamp = NSDate()
}
You can add a migration as seen in our docs and use that to take over the old values to a new property:
Objective-C
// Inside your [AppDelegate didFinishLaunchingWithOptions:]
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
config.schemaVersion = 1;
// Set the block which will be called automatically when opening a Realm with a
// schema version lower than the one set above
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// The -enumerateObjects:block: method iterates
// over every Device object stored in the Realm file
[migration enumerateObjects:Device.className
block:^(RLMObject *oldObject, RLMObject *newObject) {
// e.g. Rename 'os' to 'operatingSystem'
newObject[#"operatingSystem"] = oldObject[#"os"]
}];
}
};
// Tell Realm to use this new configuration object for the default Realm
[RLMRealmConfiguration setDefaultConfiguration:config];
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
[RLMRealm defaultRealm];
Swift (with Realm Swift)
https://realm.io/docs/swift/latest/#performing-a-migration
// Inside your application(application:didFinishLaunchingWithOptions:)
let config = Realm.Configuration(
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
schemaVersion: 1,
// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// The enumerate(_:_:) method iterates
// over every Device object stored in the Realm file
migration.enumerate(Device.className()) { oldObject, newObject in
// e.g. Rename 'os' to 'operatingSystem'
newObject["operatingSystem"] = oldObject["os"]
}
}
})
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
let realm = try! Realm()

Realm Migration Fail with Error : Migration Required, or object has already been opened with a different schema version

here is the code for the migration (in didFinishLaunchingWithOptions){
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 3,
migrationBlock: { migration, oldSchemaVersion in
// The enumerateObjects:block: method iterates
// over every 'Person' object stored in the Realm file
migration.enumerate(User.className()) { oldObject, newObject in
if oldSchemaVersion < 1 {
newObject!["crashTest"] = ""
}
if oldSchemaVersion < 2 {
}
}
}) let realm = try!Realm()
Here is the error:
fatal error: 'try!' expression unexpectedly raised an error: Error
Domain=io.realm Code=0 "Migration is required due to the following
errors:
- Property 'crashTest' has been added to latest object model." UserInfo={NSLocalizedDescription=Migration is required due to the
following errors:
- Property 'crashTest' has been added to latest object model.}: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-700.1.101.15/src/swift/stdlib/public/core/ErrorType.swift,
line 50
If you are in local development only, I suggest you to reset your realm database instead do a migration. You can reset the database by deleting the app on your simulator or devices. Alternatively you can try to use NSFileManager to delete the realm file before accessing the database.
let defaultPath = Realm.Configuration.defaultConfiguration.fileURL?.path
try! FileManager.default.removeItem(atPath: defaultPath!)

Resources