Realm-cocoa: Create a copy of a realm Object

Created on 17 Jan 2018  路  8Comments  路  Source: realm/realm-cocoa

Lets say i have an realm object stored in my db (indeed is much more complex):

class Dog: Object {
    @objc dynamic var name = ""
    @objc dynamic var age = 0
}

Now lets say i want to modify this object cause you can edit the Dog object. To verify you can actually save this object you have to call a server, and if its ok, you can save it.
The problem is that for modify the object i only have 2 options: One is importing Realm and modifying the object from the db (the option that i dont want, cause i need the object in my views and i dont want to import Realm there), and the option that im aiming for is to create a copy of the realm Object to a regular struct.

struct DogStruct {
   var name = ""
   var age = ""
}

So i can easily change those values and then if servers is ok, i can save it. There is any trick or way to do this transform? I have to manually do the transform between Dog and DogStruct and its very tedious, since Dog have nested vars and so on.

Most helpful comment

@ivangodfather we are doing something similar, in our case we wanted a detached object from Realm so we are using this (we do this when we read an object from Realm so we can use that instance from any thread):

import Realm
import RealmSwift

protocol DetachableObject: AnyObject {
  func detached() -> Self
}

extension Object: DetachableObject {
  func detached() -> Self {
    let detached = type(of: self).init()
    for property in objectSchema.properties {
      guard let value = value(forKey: property.name) else {
        continue
      }
      if let detachable = value as? DetachableObject {
        detached.setValue(detachable.detached(), forKey: property.name)
      } else { // Then it is a primitive
        detached.setValue(value, forKey: property.name)
      }
    }
    return detached
  }
}

extension List: DetachableObject {
  func detached() -> List<Element> {
    let result = List<Element>()
    forEach {
      if let detachableObject = $0 as? DetachableObject,
        let element = detachableObject.detached() as? Element {
        result.append(element)
      } else { // Then it is a primitive
        result.append($0)
      }
    }
    return result
  }
}

Then on your object (if it inherits from Object) you can just do object.detached() 馃憤

Hope it helps!

All 8 comments

Don't know if this what you need but you can use default constructor for realm object.
let copyDog = Dog(value: dog)

@mkowalski87 but you can't change those properties of copyDog without a realm write transactions, isnt?

@ivangodfather Actually you can, because such created object is detached from its realm.

@mkowalski87 seems for nested objects doesn't work :-?

after let copyDog = Dog(value: dog)

dog.name = "This works"
dog.owner.name = "This breaks"

I tried also doing copyDog.owner = Person(value: copyDog.owner) just in case before dog.owner.name but the same crash. Isnt weird?

@ivangodfather In case of relations I guess you have to implement copy method and do it manually in similar way.

@ivangodfather we are doing something similar, in our case we wanted a detached object from Realm so we are using this (we do this when we read an object from Realm so we can use that instance from any thread):

import Realm
import RealmSwift

protocol DetachableObject: AnyObject {
  func detached() -> Self
}

extension Object: DetachableObject {
  func detached() -> Self {
    let detached = type(of: self).init()
    for property in objectSchema.properties {
      guard let value = value(forKey: property.name) else {
        continue
      }
      if let detachable = value as? DetachableObject {
        detached.setValue(detachable.detached(), forKey: property.name)
      } else { // Then it is a primitive
        detached.setValue(value, forKey: property.name)
      }
    }
    return detached
  }
}

extension List: DetachableObject {
  func detached() -> List<Element> {
    let result = List<Element>()
    forEach {
      if let detachableObject = $0 as? DetachableObject,
        let element = detachableObject.detached() as? Element {
        result.append(element)
      } else { // Then it is a primitive
        result.append($0)
      }
    }
    return result
  }
}

Then on your object (if it inherits from Object) you can just do object.detached() 馃憤

Hope it helps!

@robertofrontado makes a lot of sense! thx u!

As of now, Dec 2020, there is not proper solution of this issue. We have many workarounds though.

Here one I have been using, and one with less limitations in my opinion.

  1. Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
    @objc dynamic var breed:String = "JustAnyDog"
}
  1. Create this helper class
class RealmHelper {
    //Used to expose generic 
    static func DetachedCopy<T:Codable>(of object:T) -> T?{
       do{
           let json = try JSONEncoder().encode(object)
           return try JSONDecoder().decode(T.self, from: json)
       }
       catch let error{
           print(error)
           return nil
       }
    }
}
  1. Call this method whenever you need detached / true deep copy of your Realm Object, like this:
 //Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
 guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
    print("Could not detach Note")
    return
 }
//Change/mutate object properties as you want
 detachedDog.breed = "rottweiler"

As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.

Though its NOT an ideal solution, but its one of the most effective workaround.

Was this page helpful?
0 / 5 - 0 ratings