Object already managed by another realm

First off, I love Realm for SwiftUI and it has some amazing, efficient features, which only seem to be expanding as new versions are released. Amazing work!

Having said that, I am getting increasingly exasperated with thrown errors regarding realm instances. Most prominently, I am running into numerous frustrating instances of “Object is already managed by another Realm. Use create instead to copy it into this Realm.” I have also encountered an exception similar to “Objects must be from the realm being queried”.

These exceptions seem to imply that there are other realm databases in my project, which is not the case. I use a single, local realm. No other realm is every being queried, written to, or read from. My datamodel has many connected relations (I am importing the data from an existing SQL Server) and I usually ‘reach’ objects or lists of objects through following those relations and filtering them through a query where necessary. If I need to explicitly query the realm database, for example with a primary key, I exclusively use:

let realm = try! Realm()

Nevertheless, I seem to run into objects that are seemingly managed by other realms, as indicated by the exception(s) I mentioned above.

I suspect that this is, in fact, not another realm database, but either the same instance on a different thread, or another instance? (Not really sure what an instance means in this context) Or maybe a combination of the two, or maybe something else entirely? Either way, the exception is misleading, and on top of that there is very little (if any) documentation on this scenario.

So my feedback would be to either clarify these exceptions and/or add a section in the documents; unless, of course, I’m wrong and there are in fact multiple realm databases that I’ve created inadvertently, in which case the document could also do with a little explanation on how to avoid this. In other words: I am fully ready and willing to accept that these errors are the result of my own mistakes or misuse of the framework, but the documentation is unable to either help me avoid these mistakes, or rectify them.

For my specific use case, without going into too much detail, the exception is raised at the last line of the write block. The object is created literally right before that. The object has not been added, or created, to any instance of any realm anywhere, and the initializer only assigns the parameters to properties (for now). So how, where and why is this object possibly managed by another realm?

class ProjectServiceFloor: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var guid = UUID().uuidString
    @Persisted var projectService: ProjectService!
    @Persisted var floor: Floor!
    
    ...
    
    func createInspectionPoint(of ipType: InspectionPointType) {
        let realm = try! Realm()
        let type = realm.object(ofType: InspectionPointType.self, forPrimaryKey: ipType.id)!
        try! realm.write {
            let newIp = InspectionPoint(type: type, floor: self.floor)
            let newPip = ProjectInspectionPoint(ip: newIp, project: self.projectService.project)
            let newPsip = ProjectServiceInspectionPoint(psf: self, pip: newPip)
            realm.add(newPsip) // Error thrown here
        }
    }

Note that if the code doesn’t seem very optimal, it’s because this is just one of many iterations of debugging, trial-and-error and just generally metaphorically bashing my head in over this exception. (And also this data needs to be send back to an SQL Server that expects all these objects to be instantiated).

Also, I specifically query for the type because this function is called from a UI thread, which I believe works with a separate instance? Please correct me if this is not the case.

Here’s another inexplicability:

func createFieldValue(for field: ipField) -> FieldValue {
    let fieldValue = FieldValue(point: self.point, service: self.service, field: ipField)
    
    let realm = try! Realm()
    try! realm.write {
        realm.add(fieldValue)
        // Below throws error "Cannot modify managed RLMArray outside of a write transaction."
        self.fieldValues.append(ipfv) 
    }

    return fieldValue
}

I am literally inside a write transaction? Again, I’m sure I’m using stuff wrong but these exception messages are not helpful (to say the least).

I understand your frustration and feel we can help.

The error is not indicating you have different Realm files per se - at a high level, it’s indicating an object created on one Realm instance/thread is trying to be modified on a different Realm instance/thread.

You can think of it as an object on one thread is trying to be modified by code on a different thread.

The main restriction is that you can only use an object on the thread which it was created.

There are ways around that but it’s essential to the core functionality of Realm.

The code in your question is a bit limited - and there could be a number of causes for the error.

So, first question: Is this this only way you ever open a Realm?

let realm = try! Realm()

e.g. do you ever use a realm config Realm.Configuration anywhere in your code?

The second question: Do you ever use background tasks? Often times when writing or reading a large amount of data you may want to do that in an autoreleasepool on a background thread to keep the UI fluid. It may look something like this

DispatchQueue(label: "background").async {
    autoreleasepool {
        let realm = try! Realm()

Last Question:

let newPsip = ProjectServiceInspectionPoint(psf: self, pip: newPip)
realm.add(newPsip)

How was self (the ProjectServiceFloor object) instantiated? Where did it come from? How is it being used?

1 Like

So, first question: Is this this only way you ever open a Realm?

let realm = try! Realm()

e.g. do you ever use a realm config Realm.Configuration anywhere in your code?

Yes, I only ever use this way. I have tried in the past to use the Realm? properties attached to objects but found random errors so I figured I needed to stick to one way of accessing. Possibly those errors are similar to my current problem?

As for the configuration, I do use a default configuration as seen below. The static function is called in the SceneDelegate:scene method during startup.

enum RealmMigrator {
    static private func migrationBlock(migration: Migration, oldSchemaVersion: UInt64) { 
        // Not implemented yet
    }

    static func setDefaultConfiguration() {
        let config = Realm.Configuration(
            schemaVersion: 1,
            migrationBlock: migrationBlock,
            deleteRealmIfMigrationNeeded: true
        )
        Realm.Configuration.defaultConfiguration = config
    }
}

I think I got this snippet from an example or tutorial somewhere, back when I was starting out with this project. I haven’t really looked at it since; because this project is not in production yet and the object schema is changing throughout development still, I haven’t felt a need to implement a migration block yet.

The second question: Do you ever use background tasks?

Yes, all of the data is downloaded from an API and decoded into realm objects. This is done with an async function which is called from the UI using Task.

The ProjectServiceFloor objects (as well as all existing ProjectServiceInspectionPoint objects) are not downloaded, but created after the downloaded data is decoded and added to the realm. All of this is happening in the aforementioned Task. Below is a simplified version of the request object, but really it’s just a matter of downloading and storing the data, then creating and storing these secondary objects.

struct ProjectRequest {
    
    func store<T: ResponseRow>(_ objects: [T]) {
        let realm = try! Realm()
        debugPrint("Storing \(String(describing: T.self))")
        try! realm.write {
            for row in objects {
                row.validate()
                realm.add(row, update: .modified)
            }
        }
    }
    
    ...
    
    func doRequest() async throws {
        let response = try await NetworkService.shared.api.sendRequest(request: self)
        try await processResponse(response: response)
    }

    func processResponse(response: ProjectResponse) async throws {
        // Somewhat simplified
        try await store(response.projectServices)
        try await store(response.projectInspectionPoints)
        ...
        debugPrint("Response parsed")
        
        let realm = try! await Realm()
        try! realm.write {
            for ps in realm.objects(ProjectService.self) {
                for floor in ps.floors {
                    let psf = ProjectServiceFloor(ps: ps, floor: floor)
                    ream.add(psf, update: .modified)
                    
                    for pip in ps.getPips(for: floor) {
                        let psip = ProjectServiceInspectionPoint(psf: psf, pip: pip)
                        realm.add(psip, update: .modified)
                    }
                }
            }
        }
    }
}

Does this mean that they are added into a different ‘instance’ of Realm because it is done from an async function? I know that realm objects are thread-confined, but I thought that after this background task is closed, the database would go back to a single source of truth? Admittedly I don’t know much about threading in Swift yet (I know this is outside the scope of this forum but if you know of any guides out there to give a bit more detail into how/when threads are created it would be appreciated).

Should also mention that I’m very impressed by Realm as a whole, so I don’t want to give off the vibe that I’m only complaining. When it works it’s truly amazing.

As for your last question, the createInspectionPoint method is called from a view:

struct AddInspectionPointPopover: View {
    @ObservedRealmObject var psf: ProjectServiceFloor
    @ObservedResults(InspectionPointType.self) var types
    @State var selection: InspectionPointType?
    
    ...
    
    func createIp() {
        guard let selection = selection else {
            return
        }
        
        psf.createInspectionPoint(of: selection)
        dismiss()

It’s challenging to get a good overview of the project flow but I would guess that the objects are being created one one thread but then accessed on a different thread.

I know you posted simplified code but be sure to encapsulate your try await in a do: catch block so errors are trapped.

Also, this may not needed let realm = try! await Realm(); await is generally for queries where you’re awaiting the data to be returned or long write transactions. Generally speaking, if this is a local Realm, you will rarely have to ‘wait’ for data. On the other hand - these kinds of functions are critical when working with MongoDB Realm Sync (data sync’d from the server).

I am going to take a guess here but it seems like you’re using a number of asynchronous calls, background threads and try await syntax where much of that may not be needed - and may be the core cause of the issues as objects are on different threads at different times. In other words you may have asynchronous calls within asynchronous calls; this is an example

func processResponse(response: ProjectResponse)  async throws { <- asynch
        let realm = try! await Realm() <-asynch

But again, without (a lot) more context, it’s hard to say so all of the above is a guess.