Skip to content

Upserting an object with an embedded object crashes the app #7163

@sochalewski

Description

@sochalewski

Goals

Upsert an object that contains an embedded object.

Expected Results

Upsert works flawlessly.

Actual Results

Upsert crashes an app.

2021-03-04 19:48:14.412232+0100 Example[37771:2640657] *** Terminating app due to uncaught exception 'RLMException', reason: 'Cannot set a link to an existing managed embedded object'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff20421af6 __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00007fff20177e78 objc_exception_throw + 48
	2   Realm                               0x0000000103fcddc0 _ZN18RLMAccessorContext12createObjectEP11objc_objectN5realm12CreatePolicyEbNS2_6ObjKeyE + 3568
	3   Realm                               0x000000010410859e RLMAddObjectToRealm + 302
	4   RealmSwift                          0x0000000105ad1770 $s10RealmSwift0A0V3add_6updateySo0aB6ObjectC_AC12UpdatePolicyOtF + 1392
…
)
libc++abi.dylib: terminating with uncaught exception of type NSException

Steps for others to Reproduce

  1. Create an instance of Realm database.
  2. Create an instance of object that contains an embedded object (MyObject).
  3. Set the embedded object to non-nil.
  4. Write the instance to the database.
  5. Create a copy of the first instance.
  6. Upsert the second instance into the database.

Code Sample

Models:

final class MyEmbeddedObject: EmbeddedObject {
    @objc dynamic var value = false
}

final class MyObject: Object {
    @objc dynamic var id = ObjectId.generate()
    @objc dynamic var embeddedObject: MyEmbeddedObject?
    
    override static func primaryKey() -> String? {
        return "id"
    }
}

Code:

let realm = try! Realm()

let myObject1 = MyObject()
myObject1.embeddedObject = MyEmbeddedObject()

try! realm.write {
    realm.add(myObject1)
}

let myObject2 = MyObject(value: myObject1)

try! realm.write {
    realm.add(myObject2, update: .modified) // ⚠️ Responsible line
}

Code that works, but I guess is a workaround instead of a solution.

try! realm.write {
    myObject2.embeddedObject = MyEmbeddedObject(value: myObject2.embeddedObject as Any)
    realm.add(myObject2, update: .modified)
}

I understand it relates how Object.init(value:) initializer works (shallow instead of deep copy), but as I call Realm.add(_:update:) with .modified instead of .all, I expected this to work properly.

Version of Realm and Tooling

Realm framework version: 10.7.0

Realm Object Server version: N/A

Xcode version: 12.4

iOS/OSX version: 14.4

Dependency manager + version: CocoaPods 1.10.1

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions