Copied Realm not returning data


#1

I am having an issue accessing data in a copied Realm.

In my app I have three Save slots which store saved Realms of my app’s data. These are all stored in the application directory. I am able to read from these saved Realms fine.

My issue occurs when the user selects the saved Realm to reload.

My code deletes the default.realm file and makes new default.realm file by copying the saved Realm. The file copies fine, I can see it in Finder, I can open it in Realm Studio and see it contains the same data as the original. I can even set the Realm Configuration to use the new default.realm file without errors.

However any query returns an “Unexpectedly found nil” error.

Is there a step I am missing in the copying of the realm? Do I need to set any permissions on the file? This is driving me mad.

My functions’s code is below. My CustomFunction.migrateRealm performs the realm migration as per standard.

@IBAction func btnSlotLoad(_ sender: Any) {
    
    // get the URL to load from
    let slotNumber = (sender as AnyObject).tag!
    let slotName   =  "Game\(slotNumber).realm"
    let slotURL    = getRealmURL(realmName: slotName)
    print ("btnSlotLoad will load from Realm: ", slotURL)
    
    var realm      = try! Realm()
    var gameData    : GameData
    
    //Check Realm version
    _ = Realm.Configuration()
    // get default URL for this app's instance of Realm
    let defaultURL =  Realm.Configuration.defaultConfiguration.fileURL!
    print ("Current Game URL: ", defaultURL)
    
    try! realm.write {
        realm.deleteAll()
    }
    //Deallocate realm
    
    let realmURLs = [
        defaultURL,
        defaultURL.appendingPathExtension("lock"),
        defaultURL.appendingPathExtension("note"),
        defaultURL.appendingPathExtension("management")
    ]
    for URL in realmURLs {
        do {
            try FileManager.default.removeItem(at: URL)
        } catch {
            print("Error deleting Realm file: \(URL)")
        }
    }
    
    //Load Realm
    do {
        try FileManager.default.copyItem(at: slotURL, to: defaultURL)
    } catch {
        print("Error loading Realm, \(error)")
    }
    
    let loadedRealmConfig = CustomFunction.migrateRealm(realmName: "default.realm")
    realm = try! Realm(configuration: loadedRealmConfig)
    
    //Code is failing with fatal error here
    gameData = try! realm.objects(GameData.self).first!
    
    self.performSegue(withIdentifier: "ContinueGameFromVC", sender: self)
}

#2

Why are you deleting and creating Realms in the first place? What does the migration have to do with creating a Realm. i.e. if you totally delete a realm, there would be no reason to have a migration since there’s nothing to migrate to, all of your data would be fresh in a new Realm anyway.


#3

Hi Jay, I added the migration to ensure that any previously saved Realms that may have last been opened before any schema changes were properly migrated.
Apologies for the duplication with SO, my first post in either forum so please excuse my ignorance on inexperience. Any way I can delete this?


#4

No worries and I believe you can delete you own posts by clicking the small trash can in the lower corner of the post.

I think I would leave it in place though and add a link to your post on SO so others can follow the breadcrumbs in case they have a similar question.


#5

Afraid I am still having issues trying to copy Realms.

My use case is very straightforward. My app is a game. When a user wants to start a new game, the game data needs to be returned to the initial state.

I have observed the following:

  • I am able to delete the old Realm and all associated lock files.
  • I am able to delete all old Realm objects from memory.
  • I am able to copy over the bundled Realm to my app’s directory.

However, if I copy over the bundled Realm to the same URL I have used previously, the Realm appears empty.
If I use a different URL, the Realm displays data.

In both of the above cases, I can view the same data via Realm Studio - the only difference occurs when running the app.

This is a problem, because ideally I want to use the same URL (to default.realm) without having to worry about it. Is this a known bug? if so is there a fix?

My code is below.

func okPressed() {
    
    // get default URL for this app's instance of Realm
    print ("Realm.Configuration.defaultConfiguration: ", Realm.Configuration.defaultConfiguration.description)
    let defaultURL =  Realm.Configuration.defaultConfiguration.fileURL!.deletingLastPathComponent().appendingPathComponent("default.realm")
    // set new game's URL : NOTE this code only works if the newGameURL is different to the defaultURL
    let newGameURL =  Realm.Configuration.defaultConfiguration.fileURL!.deletingLastPathComponent().appendingPathComponent("newgame.realm")
    let bundledRealmURL = Bundle.main.url(forResource: "default", withExtension: "realm")
    
    // Perform any migrations required
    let migrationBlock : MigrationBlock = { migration, oldSchemaVersion in
        if (oldSchemaVersion < 17) {
        }
    }
    // Ensure current Realm Config is set for Realm at default URL (which we want to delete)
    let currentRealmConfig = Realm.Configuration(fileURL: defaultURL, schemaVersion: 17, migrationBlock: migrationBlock, deleteRealmIfMigrationNeeded: true)
    Realm.Configuration.defaultConfiguration = currentRealmConfig
    
    // Delete all objects in the current Realm configuration
    autoreleasepool {
        let realm = try! Realm()
        try! realm.write {
            realm.deleteAll()
        }
    }
    
    // Remove existing default.realm and related lock files
    let realmURLs = [
        defaultURL,
        defaultURL.appendingPathExtension("lock"),
        defaultURL.appendingPathExtension("note"),
        defaultURL.appendingPathExtension("management"),
    ]
    realmURLs.forEach { URL in
        guard FileManager.default.fileExists(atPath: URL.path) else { return }
        do {
            try FileManager.default.removeItem(at: URL)
        } catch {
        }
        print("Removed file: ", String(describing: URL))
    }
    
    // Copy over bundled Realm to new Game URL
    do {
        try FileManager.default.copyItem(at: bundledRealmURL!, to: newGameURL)
    } catch {
        print("Error loading Realm, \(error)")
    }
    
    // Set Realm default configuration for new game
    let newGameConfig = Realm.Configuration(fileURL: newGameURL, schemaVersion: 17, migrationBlock: migrationBlock, deleteRealmIfMigrationNeeded: true)
    Realm.Configuration.defaultConfiguration = newGameConfig
    
    // Disable file protection for this directory
    let folderPath = newGameConfig.fileURL!.deletingLastPathComponent().path
    try! FileManager.default.setAttributes([.protectionKey: FileProtectionType.none], ofItemAtPath: folderPath)
    
    do {
        let newRealm = try! Realm()
        var gameArray = newRealm.objects(GameData.self)
        print ("gameData in new Realm:", gameArray as Any)
    } catch let error as NSError {
        print("Error opening newRealm: \(error)")
    }
    
    self.performSegue(withIdentifier: "startNewGame", sender: self)
}

#6

should be

let realmURL = Realm.Configuration.defaultConfiguration.fileURL!

and then

let realmURLs = [
    realmURL,
    realmURL.appendingPathExtension("lock"),
    realmURL.appendingPathExtension("note"),
    realmURL.appendingPathExtension("management")
]
for URL in realmURLs {
    do {
        try FileManager.default.removeItem(at: URL)
    } catch {
        // handle error
    }
}

I use the above code to delete and write to the default realm all the time.


#7

Thank Jay, afraid that hasn’t worked for me. The same result occurs - I can see the default.realm in Finder, I can open it in Realm Studio and see it contains data, but the query in Swift still returns nil.

For reference the new default configuration is

newGameConfig: Realm.Configuration {
fileURL = file:///Users/richard/Library/Developer/CoreSimulator/Devices/E87C0BB6-CDB5-45D1-8F96-E812AFCAA95B/data/Containers/Data/Application/76C32AFC-E47E-41FB-AE74-2BD30BA734D0/Documents/default.realm;
inMemoryIdentifier = (null);
encryptionKey = (null);
readOnly = 0;
schemaVersion = 17;
migrationBlock = <NSMallocBlock: 0x600003dee100>;
deleteRealmIfMigrationNeeded = 1;
shouldCompactOnLaunch = (null);
dynamic = 0;
customSchema = (null);