Realm-cocoa: setValue forKey not working with List

Created on 27 Oct 2017  路  17Comments  路  Source: realm/realm-cocoa

I have an object in Realm. I would like to retrieve that object and then work with an unmanaged version of that object. Unfortunately, it doesn't seem like Realm for iOS has any good copy options, so I tried to followed the "detachable" workaround by "anlaital" outlined in issue #3381.

My code is as follows:
screen shot 2017-10-27 at 3 53 33 pm

This works for the most part. When on a property that is a list and has values, detachable.detached() does return the expect list copy. Unfortunately, detached.setValue(detachable.detached(), forKey: property.name) does not set the value for any Lists my object has.
screen shot 2017-10-27 at 3 54 39 pm

Any thoughts on how I can properly assign my "detached"/unmanaged list to my object?

T-Help

Most helpful comment

I actually have a slightly newer version of this.

```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 property.isArray == true {
            //Realm List property support
            let detachable = value as? DetachableObject
            detached.setValue(detachable?.detached(), forKey: property.name)
        } else if property.type == .object {
            //Realm Object property support
            let detachable = value as? DetachableObject
            detached.setValue(detachable?.detached(), forKey: property.name)
        } else {
            detached.setValue(value, forKey: property.name)
        }
    }
    return detached
}

}

extension List: DetachableObject {
func detached() -> List {
let result = List()

    forEach {
        if let detachable = $0 as? DetachableObject {
            let detached = detachable.detached() as! Element
            result.append(detached)
        } else {
            result.append($0) //Primtives are pass by value; don't need to recreate
        }
    }

    return result
}

func toArray() -> [Element] {
    return Array(self.detached())
}

}

extension Results {
func toArray() -> [Element] {
let result = List()

    forEach {
        result.append($0)
    }

    return Array(result.detached())
}

}

All 17 comments

Realm lists don't support reassignment. This is why we recommend that they be declared as let rather than var; anything you can do with a var list you can't do with a let list is considered erroneous behavior.

Alright; as an alternative, is there a way to append to a list by key?

For what it's worth, I've had success with this approach:
screen shot 2017-10-27 at 11 23 22 pm

I'm appending to lists (reference grabbed via getValue:forKey: ). The unfortunate part is I must be quite verbose when checking list types (note the as? List<Int>, as? List<String, and so on).

I'm glad you figured out something that worked, and I hope others can benefit from your approach! Feel free to open new tickets if you have additional questions.

@austinzheng You comment:

Realm lists don't support reassignment

Seems to contradict the documentation:

You can access and assign List properties as usual:

Am I missing it in the docs, or is it simply a little vague?

The documentation is either unclear or wrong. I think the original intent was to say you can modify the elements inside a List, including assigning new objects to a particular index, rather than assigning a new List instance altogether. I'll update it, thanks for bringing it to our attention.

@allenhumphreys Comment on my other issue (#5469) gave me a magnificent idea.
Instead of having the messy casting for lists like I do in post 4 (which incidentally crashes for objects), because this is a detached/unmanaged object, I should be able to safely do setValueForKey for lists.

I tested this and it seemed to work, but I would like to hear your thoughts @austinzheng.
Do you think it is safe to set/assign a list in this manner on the premise that everything is unmanaged?

screen shot 2017-11-15 at 12 51 15 pm

If it's unmanaged I don't see why it would be problematic. Does it work?

Indeed it does. I ran a few test cases and they all seemed to work beautifully. However, I do look forward to using this in a project and, effectively, testing it more broadly.

I appreciate your input.

Thank you @Alarson93 this was much helpful :)
This solution works indeed.

If anyone needs the code from @Alarson93, especially when dealing with #3381 (which "accepted" is broken since it doesn't support List):

import Realm
import RealmSwift

public extension Object {
  public func detached() -> Self {
    let detached = type(of: self).init()
    for property in objectSchema.properties {
      guard let value = value(forKey: property.name) else { continue }
      if property.isArray == true || property.type == .object {
        // Realm List and Realm Objects
        let detachable = value as? Object
        detached.setValue(detachable?.detached(), forKey: property.name)
      } else {
        // Primitives
        detached.setValue(value, forKey: property.name)
      }
    }
    return detached
  }
}

public extension Sequence where Iterator.Element : Object  {
  public func detached() -> [Element] {
    return self.map({ $0.detached() })
  }
}

public extension List {
  public func detached() -> List<Element> {
    let result = List<Element>()
    self.forEach { element in
      if let element = element as? Object {
        // Realm Objects
        result.append(element.detached() as! Element)
      } else {
        // Primitives
        result.append(element)
      }
    }
    return result
  }
}

I actually have a slightly newer version of this.

```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 property.isArray == true {
            //Realm List property support
            let detachable = value as? DetachableObject
            detached.setValue(detachable?.detached(), forKey: property.name)
        } else if property.type == .object {
            //Realm Object property support
            let detachable = value as? DetachableObject
            detached.setValue(detachable?.detached(), forKey: property.name)
        } else {
            detached.setValue(value, forKey: property.name)
        }
    }
    return detached
}

}

extension List: DetachableObject {
func detached() -> List {
let result = List()

    forEach {
        if let detachable = $0 as? DetachableObject {
            let detached = detachable.detached() as! Element
            result.append(detached)
        } else {
            result.append($0) //Primtives are pass by value; don't need to recreate
        }
    }

    return result
}

func toArray() -> [Element] {
    return Array(self.detached())
}

}

extension Results {
func toArray() -> [Element] {
let result = List()

    forEach {
        result.append($0)
    }

    return Array(result.detached())
}

}

Hi, can someone translate @Alarson93's solution in Objective C.

Good solution, almost all our realm related crashes are gone, except that now our app crash Object has been deleted or invalidated is pointing to the detaching process for the value getter:
guard let value = value(forKey: property.name) else { continue }

@loilee I am also getting this kind of crash. Have you reported it or solved it?

@Alarson93 ,

Your solution doesn't work if A's child object has A as a child.

@winstondu

Can you provide a simple example? (Pseudo-code is fine)
Also - what happens? Does it simply not map, does it crash, etc?

Was this page helpful?
0 / 5 - 0 ratings