Search code examples
xcodeswift2realmrealm-migration

Realm.io RealmSwift Other Realms Migration


I need to do a migration for my Other Realm .

I get my realm path via this method (AppDelegate). If a user logged in before, i will retrieve the user's realm else i will just use the Default Realm.

 func getRealmPath() -> String {
    let preferences : NSUserDefaults = NSUserDefaults()
    let username = preferences.objectForKey(usernameKey) as! String?

    if username != nil {
        let realmName =  ("\(username!).realm")

        print("RealmName: \(realmName)", terminator: "")

        let documents = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
        return documents.stringByAppendingPathComponent(realmName)
    }else{
        return Realm.Configuration.defaultConfiguration.path!
    }  
}

I did my migration via this method (called inside AppDelegate:didFinishLaunchingWithOptions) .

func updateRealm(){

    let config = Realm.Configuration(path: getRealmPath(), schemaVersion: 2, migrationBlock: { (migration, oldSchemaVersion) -> Void in

        print("oldSchemaVersion \(oldSchemaVersion)") 

         migration.create("RLMStringTimestamp", value: ["pKey": NSUUID().UUIDString, "value": "", "updatedAt": NSDate(), "createdAt": NSDate(), "deletedAt": Date().getInitDate(), "updatedBy" : " ", "syncedAt": NSDate() ])           

        if oldSchemaVersion < 2  {

           //MIGRATION
           let firstNameTimeStamp =  RLMStringTimestamp(newValue: oldObject!["firstName"] as? String)
           migration.create("RLMStringTimestamp", value: firstNameTimeStamp)
           newObject!["firstName"] = firstNameTimeStamp

        }
      }

      Realm.Configuration.defaultConfiguration = config

      //EDIT 2 
      Realm.Configuration.defaultConfiguration.path = getRealmPath()

     //EDIT 1
     //<strike>let realm = try! Realm(path: getRealmPath())</strike>

     //EDIT 4 
     print(Realm.Configuration.defaultConfiguration)

     //EDIT 3
     let realm = try! Realm()
     }

For my RLMCustomer Object, i modified var firstName: String = "" to var firstName: RLMStringTimeStamp!

Even if i change the schemaVersion to something very high, the migrationBlock didn't get call. Can anyone help me spot what am i missing or doing wrong?

After running the app, it crashes with bad excess, code = 257

EDIT 1:

error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=0 "Provided schema version 0 is less than last set version 1." UserInfo=0x170660c00 {NSLocalizedDescription=Provided schema version 0 is less than last set version 1.}: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-700.0.59/src/swift/stdlib/public/core/ErrorType.swift, line 50

It seems to be reading the wrong configuration file and i suspect the error is due to Realm.Configuration.defaultConfiguration = config How do i set the configuration for Other Realm?

EDIT 2:

I make my default realm to contains the name and path of my other realm

EDIT 4:

It seems that the configuration file is correct. I am able to run the app without problem if there's no customer record from the old realm. It will only crash when there's customer record in the old realm. I can get the values from the oldObject["firstName"]

print(Realm.Configuration.defaultConfiguration)

Realm.Configuration { path = /var/mobile/Containers/Data/Application/8670C084-75E7-4955-89FB-137620C9B00D/Documents/perwyl.realm; inMemoryIdentifier = (null); encryptionKey = (null); readOnly = 0; schemaVersion = 2; migrationBlock = <NSMallocBlock: 0x170451220>; dynamic = 0; customSchema = (null); } oldSchemaVersion 0

Much Thanks!!!

EDIT 5: Solution to my problem

I'm not sure why it crashes if i assign StringTimestamp object directly to newObject.

let firstName = (oldObject!["firstName"] as? String)!
let firstNameTimeStamp =  StringTimestamp(newValue: firstName)
let testName = migration.create("StringTimestamp",value:     firstNameTimeStamp)
newObject!["firstName"] = firstNameTimeStamp //Crashes
newObject!["firstName"] = testName //works 

Thanks for everyone's guidelines! :)


Solution

  • Why not letting realm use the default configuration? Since you set the path in default configuration to getRealmPath(), it should be ok to do just:

     let realm = try! Realm()
    

    By instantiating the realm with Realm(path: getRealmPath()) you are overriding the defaultConfiguration you had previously set. That is, the path of the realm becomes getRealmPath(), but all other properties you set in config are lost, and default values are used instead. This includes schemaVersion = 0 and migrationBlock = nil.

    The intent of initializers like Realm(path:) and Realm(configuration:) is to allow you to use alternative configurations other than the default. If what you want is to use a modified version of the default configuration, then you would need to do something on the lines of:

    // Get a copy of the default configuration
    var otherConfig = Realm.Configuration.defaultConfiguration
    // Update the copy with what you need
    otherConfig.path = "/some/path/otherRealm.realm"
    // Use the updated configuration to instantiate a realm
    let otherRealm = try! Realm(configuration: otherConfig)
    

    One neat way to debug realm configuration problems is by setting breakpoints or printing logs before instantiating the realm. Running the following code lets me know what is the default configuration about to be used.

    print(Realm.Configuration.defaultConfiguration))
    let realm = try! Realm()
    

    The output looks something like

    Realm.Configuration {
        path = /Users/<full-path-omitted>/edman.realm;
        schemaVersion = 2;
        migrationBlock = <__NSMallocBlock__: 0x7faee04ac590>;
        // other properties...
    }
    

    By looking at it I'm sure my realm was instantiated with the path for edman.realm, schemaVersion = 2, and it has non-nil migrationBlock.