Set unmanaged property via index

Can an unmanaged property for an Object in List be set via the Objects index?

For example suppose there’s a Person object with a List of Dogs

class Person: Object {
   @Persisted var name = ""
   @Persisted var dogList = RealmSwift.List<Dog>()
}

class Dog: Object {
   @Persisted var name = ""
   var unmanagedVar = false
}

and a person object with two dogs is instantiated and written to realm.

Then the person object is read in

let jay = realm.objects(Person.self).where { $0.name == "Jay" }.first!

and here’s the question; why is the behavior by referencing the object by it’s index and setting the property different than getting the object as a var and setting the property

jay.dogList[1].unmanagedVar = true  //set the unmanagedVar to true via its index
print(jay.dogList[1].unmanagedVar)  // output is false ?!?!?!
        
let scraps = jay.dogList[1]         //get the object as a var
scraps.unmanagedVar = true          //set the unmanagedVar to true
print(scraps.unmanagedVar)          // output is true ?!?!?!

I know Realm “ignores” read-only properties but this indicates it ignores ALL non-managed properties when referencing the object via the index in a list. What am I overlooking?

Every time you access an element in the list you get a new instance of the Dog object:

let dog1 = jay.dogList[1]
let dog2 = jay.dogList[1]

dog1 === dog2 // false

Thanks - yes. that’s expected behavior.

What isn’t expected is the property NOT being set on the same object - or is it somehow a different object? If so, how does one reference that objects unmanaged properties?

jay.dogList[1].unmanagedVar = true  //set the unmanagedVar to true via its index
print(jay.dogList[1].unmanagedVar)  // output is false ?!?!?!

Not sure I fully understand what you’re asking, but I’ll try to elaborate on my answer. In the example above both dog1 and dog2 point to the same persisted object, but are different swift instances. That’s why the persisted properties are the same - they give you the value that is persisted in the database, but the unmanaged properties are independent - they just give you whatever is stored in the memory occupied by the respective instance.

When you assign an element from the list to a variable and reference only that variable, you’re working with the same instance, so the unmanaged property retains the value that you assign it. Here are some examples that will hopefully help build some intuition around it:

jay.dogList[1] === jay.dogList[1] // false -> each element access produces a different instance
let dog1 = jay.dogList[1]
dog1 === dog1 // true
dog1.unmanagedVar = true
dog1.unmanagedVar // still true

jay.dogList[1].unmanagedVar // false - a different instance
let dog2 = jay.dogList[1]
dog1 === dog2 // false
dog2.unmanagedVar // false - no one has set the unmanaged var on dog2

This is glossing over some of the internals, but essentially what happens when you do jay.dogList[1] looks like this:

let nativeObject  = jay.dogList.internalNativeList.getElementAt(1)
let result = new Dog()
result.assignNativeAccessor(nativeObject)
return result

and when you access values on the dog, it looks like:

dog.name -> dog.nativeAccessor.getProperty("name")
dog.unmanagedVar -> dog.unmanagedVar // returns the value stored by the property

This is why every time you get a persisted object - e.g. by doing realm.objects(...).first or foo.listOfBars[5] or foo.bar, the unmanaged properties on it are set to their default values.

let me ask it this way. In this one line piece of code which attempts to set the unmanagedVar using . notation:

what is the value of unmanagedVar? true or false?

The reason I ask is that I was expecting to be able to change the unmanaged property on the Realm object using . notation but that doesn’t appear to work.

Here’s a Swift example which works as expected:

class Person {
    var name = ""
    var dogs = [Dog]()
}

class Dog {
    var name = ""
}

let p = Person()
p.name = "Jay"

let d0 = Dog()
d0.name = "Spot"

let d1 = Dog()
d1.name = "Scraps"

p.dogs.append(contentsOf: [d0, d1])

p.dogs.forEach { dog in
    print(dog.name) //outputs Spot and Scraps
}

//now change the name of a dog using . notation in the array
print("---")

p.dogs[1].name = "Rover" //using . notation similar to `jay.dogList[1].unmanagedVar = true`

p.dogs.forEach { dog in
    print(dog.name) //the dog name at index 1 has changed!
                   // This outputs Spot and *Rover*
}

Does that clarify the question?

The difference is that with the plain Swift class, the array is fully materialized and it’s contents are in memory. So every time you access an element in the array you get the same instance. This is not the case with Realm so every time you access it, you get a different instance. When you’re setting a value like in your example, what is happening is that a Dog instance is allocated, the unmanagedVar property is set, then the instance is thrown away. The next time you access the element at the same index, a new Dog instance is created and all its unmanaged fields are set to their default values.

Thank you for the clarification - I thought it was something along those lines but it’s not really addressed in the documentation.

Perhaps that would be a suggestion and or clarification to the docs.

“Updating an objects non-managed property when the object is stored in a List will not update that property. An instance of that object must be created first, then the object can be updated via dot notation”

or something along those lines. It’s obvious to you but that’s an important difference in how Realm objects behave vs a Swift object.

Thanks for the help!

On this topic - a followup question:

How does one find the index of a known embedded object that’s contained in a List?

For example a Person class has a number of embedded Dog objects in a List

//get the dog at index 1
let dogAtIndex_1 = jay.embeddedDogList[1]  

//try to find that dog
let shouldBeIndex_1 = jay.embeddedDogList.firstIndex(of: dogAtIndex_1)

but the shouldBeIndex_1 is nil. Is there any way to locate that dogs index in the List?

@nirinchev answering my own question, and thanks again for your help.

Two solutions, and I am sure there are more.

  1. Add a unique identifier to the embedded objects which can then be used to find the object being looked for. This is pretty obvious but posting for clarity
class EmbeddedDog: EmbeddedObject {
    @Persisted var dog_id: ObjectId
    @Persisted var dogName = ""
}
//get the dog at index 1
let dogList = jay.embeddedDogList
let dogAtIndex_1 = dogList[1] 
let dogId = dogAtIndex_1.dog_id
let dogIndex = dogList.firstIndex(where: { $0.dog_id == dogId } ) //results in dogIndex = 1
  1. Cast the List to an Array (which then occupies memory and looses the lazy-loading Realm behavior) and then the index can be located by the object itself. This may be the not-so-obvious solution
let dogList = jay.embeddedDogList
let dogArray = Array(dogList)
let dogAtIndex_1 = dogArray[1] 
let dogIndex = dogArray.firstIndex(of: dogAtIndex_1) //results in dogIndex = 1

Note that casting a Realm collection to an Array then occupies memory. If the list of EmbeddedObjects is large, this can be dangerous and overwhelm the device. Use with caution!