RealmSwift @ObservedResults with parent and child models, cannot modify managed RLMArray outside of a write transaction

Viewed 809

I am trying to display some simple Realm objects in a SwiftUI list. Each object also has related child objects.

I am able to display the parent objects just fine by using the @ObservedResults(Parent.self) property wrapper. Simply adding or removing the parent objects to/from Realm updates the list and this works great.

The problem occurs when I want to display the related child models. When I attempt to add the child models in Realm the same way I added the parent models, also using the default Realm.Configuration, I get an RLMException saying Cannot modify managed RLMArray outside of a write transaction.

Here is a simple SwiftUI view that illustrates the problem.

struct ContentView: View {    
    
    @ObservedResults(Parent.self) var parents
    
    var body: some View {
        VStack(spacing: 15) {
            Button("Create parents") {
                let realm = try! Realm()
                try! realm.write {
                    realm.add(Parent(id: 1, name: "Nick"), update: .all)
                    realm.add(Parent(id: 2, name: "Lucas"), update: .all)
                    realm.add(Parent(id: 3, name: "Ryan"), update: .all)
                }
            }
            Button("Create child") {
                guard let parent = parents.first else {
                    return
                }
                let realm = try! Realm()
                try! realm.write {
                    let child = Child(id: parent.children.count + 1, name: "Foo")
                    parent.children.append(child)
                }
            }
            Button("Delete all") {
                let realm = try! Realm()
                try! realm.write {
                    realm.deleteAll()
                }
            }
            List(parents) { parent in
                VStack {
                    Text(parent.name)
                    ForEach(parent.children){ child in
                        Text("Child: \(child.name)")
                            .padding(.leading, 10)
                    }
                }
            }
        }
    }
}

And here are my models:

class Parent: Object, ObjectKeyIdentifiable {
    
    @objc dynamic var id: Int = 0
    @objc dynamic var name: String = ""
    
    var children = List<Child>()
    
    override class func primaryKey() -> String? {
        return "id"
    }
 
    convenience init(id: Int, name: String) {
        self.init()
        self.id = id
        self.name = name
    }
}

class Child: Object, ObjectKeyIdentifiable {
    
    @objc dynamic var id: Int = 0
    @objc dynamic var name: String = ""
    
    private let parents = LinkingObjects(fromType: Parent.self, property: "children")
        
    override class func primaryKey() -> String? {
        return "id"
    }
    
    convenience init(id: Int, name: String) {
        self.init()
        self.id = id
        self.name = name
    }
}

By clicking the buttons Create parents and Delete all you see the parent models appear and disappear as expected. Clicking Create child produces the error.

I'm really at a loss here, any help with this is much appreciated. Thanks!

1 Answers

Answering my own question. It seems you have to thaw the parent model (whatever that means) before appending any children to it. I couldn't find this mentioned in the docs anywhere so it might be a bug.

Anyways, here is the fix:

Button("Create child") {
    guard let parent = parents.first?.thaw() else {
        return
    }
    let realm = try! Realm()
    try! realm.write {
        let child = Child(id: parent.children.count + 1, name: "Foo")
        parent.children.append(child)
    }
}
Related