//////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// import XCTest import RealmSwift class ThreadSafeReferenceTests: TestCase { /// Resolve a thread-safe reference confirming that you can't resolve it a second time. func assertResolve(_ realm: Realm, _ reference: ThreadSafeReference) -> T? { XCTAssertFalse(reference.isInvalidated) let object = realm.resolve(reference) XCTAssert(reference.isInvalidated) assertThrows(realm.resolve(reference), reason: "Can only resolve a thread safe reference once") return object } func testInvalidThreadSafeReferenceConstruction() { let stringObject = SwiftStringObject() let arrayParent = SwiftArrayPropertyObject(value: ["arrayObject", [["a"]], []]) let arrayObject = arrayParent.array assertThrows(ThreadSafeReference(to: stringObject), reason: "Cannot construct reference to unmanaged object") assertThrows(ThreadSafeReference(to: arrayObject), reason: "Cannot construct reference to unmanaged object") let realm = try! Realm() realm.beginWrite() realm.add(stringObject) realm.add(arrayParent) realm.deleteAll() try! realm.commitWrite() assertThrows(ThreadSafeReference(to: stringObject), reason: "Cannot construct reference to invalidated object") assertThrows(ThreadSafeReference(to: arrayObject), reason: "Cannot construct reference to invalidated object") } func testInvalidThreadSafeReferenceUsage() { let realm = try! Realm() realm.beginWrite() let stringObject = realm.create(SwiftStringObject.self, value: ["stringCol": "hello"]) assertThrows(ThreadSafeReference(to: stringObject), reason: "Cannot obtain thread safe reference during a write transaction") try! realm.commitWrite() let ref1 = ThreadSafeReference(to: stringObject) let ref2 = ThreadSafeReference(to: stringObject) let ref3 = ThreadSafeReference(to: stringObject) dispatchSyncNewThread { self.assertThrows(self.realmWithTestPath().resolve(ref1), reason: "Cannot resolve thread safe reference in Realm with different configuration than the source Realm") let realm = try! Realm() realm.beginWrite() self.assertThrows(realm.resolve(ref2), reason: "Cannot resolve thread safe reference during a write transaction") self.assertThrows(realm.resolve(ref2), reason: "Can only resolve a thread safe reference once") realm.cancelWrite() self.assertThrows(realm.resolve(ref2), reason: "Can only resolve a thread safe reference once") // Assert that we can resolve a different reference to the same object. XCTAssertEqual(self.assertResolve(realm, ref3)!.stringCol, "hello") } } func testPassThreadSafeReferenceToDeletedObject() { let realm = try! Realm() let intObject = SwiftIntObject() try! realm.write { realm.add(intObject) } let ref1 = ThreadSafeReference(to: intObject) let ref2 = ThreadSafeReference(to: intObject) XCTAssertEqual(0, intObject.intCol) try! realm.write { realm.delete(intObject) } dispatchSyncNewThread { let realm = try! Realm() XCTAssertEqual(self.assertResolve(realm, ref1)!.intCol, 0) realm.refresh() XCTAssertNil(self.assertResolve(realm, ref2)) } } func testPassThreadSafeReferencesToMultipleObjects() { let realm = try! Realm() let (stringObject, intObject) = (SwiftStringObject(), SwiftIntObject()) try! realm.write { realm.add(stringObject) realm.add(intObject) } let stringObjectRef = ThreadSafeReference(to: stringObject) let intObjectRef = ThreadSafeReference(to: intObject) XCTAssertEqual("", stringObject.stringCol) XCTAssertEqual(0, intObject.intCol) dispatchSyncNewThread { let realm = try! Realm() let stringObject = self.assertResolve(realm, stringObjectRef)! let intObject = self.assertResolve(realm, intObjectRef)! try! realm.write { stringObject.stringCol = "the meaning of life" intObject.intCol = 42 } } XCTAssertEqual("", stringObject.stringCol) XCTAssertEqual(0, intObject.intCol) realm.refresh() XCTAssertEqual("the meaning of life", stringObject.stringCol) XCTAssertEqual(42, intObject.intCol) } func testPassThreadSafeReferenceToList() { let realm = try! Realm() let company = SwiftCompanyObject() try! realm.write { realm.add(company) company.employees.append(SwiftEmployeeObject(value: ["name": "jg"])) } XCTAssertEqual(1, company.employees.count) XCTAssertEqual("jg", company.employees[0].name) let listRef = ThreadSafeReference(to: company.employees) dispatchSyncNewThread { let realm = try! Realm() let employees = self.assertResolve(realm, listRef)! XCTAssertEqual(1, employees.count) XCTAssertEqual("jg", employees[0].name) try! realm.write { employees.removeAll() employees.append(SwiftEmployeeObject(value: ["name": "jp"])) employees.append(SwiftEmployeeObject(value: ["name": "az"])) } XCTAssertEqual(2, employees.count) XCTAssertEqual("jp", employees[0].name) XCTAssertEqual("az", employees[1].name) } XCTAssertEqual(1, company.employees.count) XCTAssertEqual("jg", company.employees[0].name) realm.refresh() XCTAssertEqual(2, company.employees.count) XCTAssertEqual("jp", company.employees[0].name) XCTAssertEqual("az", company.employees[1].name) } func testPassThreadSafeReferenceToResults() { let realm = try! Realm() let allObjects = realm.objects(SwiftStringObject.self) let results = allObjects .filter("stringCol != 'C'") .sorted(byKeyPath: "stringCol", ascending: false) let resultsRef = ThreadSafeReference(to: results) try! realm.write { realm.create(SwiftStringObject.self, value: ["A"]) realm.create(SwiftStringObject.self, value: ["B"]) realm.create(SwiftStringObject.self, value: ["C"]) realm.create(SwiftStringObject.self, value: ["D"]) } XCTAssertEqual(4, allObjects.count) XCTAssertEqual(3, results.count) XCTAssertEqual("D", results[0].stringCol) XCTAssertEqual("B", results[1].stringCol) XCTAssertEqual("A", results[2].stringCol) dispatchSyncNewThread { let realm = try! Realm() let results = self.assertResolve(realm, resultsRef)! let allObjects = realm.objects(SwiftStringObject.self) XCTAssertEqual(0, allObjects.count) XCTAssertEqual(0, results.count) realm.refresh() XCTAssertEqual(4, allObjects.count) XCTAssertEqual(3, results.count) XCTAssertEqual("D", results[0].stringCol) XCTAssertEqual("B", results[1].stringCol) XCTAssertEqual("A", results[2].stringCol) try! realm.write { realm.delete(results[2]) realm.delete(results[0]) realm.create(SwiftStringObject.self, value: ["E"]) } XCTAssertEqual(3, allObjects.count) XCTAssertEqual(2, results.count) XCTAssertEqual("E", results[0].stringCol) XCTAssertEqual("B", results[1].stringCol) } XCTAssertEqual(4, allObjects.count) XCTAssertEqual(3, results.count) XCTAssertEqual("D", results[0].stringCol) XCTAssertEqual("B", results[1].stringCol) XCTAssertEqual("A", results[2].stringCol) realm.refresh() XCTAssertEqual(3, allObjects.count) XCTAssertEqual(2, results.count) XCTAssertEqual("E", results[0].stringCol) XCTAssertEqual("B", results[1].stringCol) } func testPassThreadSafeReferenceToLinkingObjects() { let realm = try! Realm() let dogA = SwiftDogObject(value: ["dogName": "Cookie", "age": 10]) let unaccessedDogB = SwiftDogObject(value: ["dogName": "Skipper", "age": 7]) // Ensures that a `LinkingObjects` without cached results can be handed over try! realm.write { realm.add(SwiftOwnerObject(value: ["name": "Andrea", "dog": dogA])) realm.add(SwiftOwnerObject(value: ["name": "Mike", "dog": unaccessedDogB])) } XCTAssertEqual(1, dogA.owners.count) XCTAssertEqual("Andrea", dogA.owners[0].name) let ownersARef = ThreadSafeReference(to: dogA.owners) let ownersBRef = ThreadSafeReference(to: unaccessedDogB.owners) dispatchSyncNewThread { let realm = try! Realm() let ownersA = self.assertResolve(realm, ownersARef)! let ownersB = self.assertResolve(realm, ownersBRef)! XCTAssertEqual(1, ownersA.count) XCTAssertEqual("Andrea", ownersA[0].name) XCTAssertEqual(1, ownersB.count) XCTAssertEqual("Mike", ownersB[0].name) try! realm.write { (ownersA[0].dog, ownersB[0].dog) = (ownersB[0].dog, ownersA[0].dog) } XCTAssertEqual(1, ownersA.count) XCTAssertEqual("Mike", ownersA[0].name) XCTAssertEqual(1, ownersB.count) XCTAssertEqual("Andrea", ownersB[0].name) } XCTAssertEqual(1, dogA.owners.count) XCTAssertEqual("Andrea", dogA.owners[0].name) XCTAssertEqual(1, unaccessedDogB.owners.count) XCTAssertEqual("Mike", unaccessedDogB.owners[0].name) realm.refresh() XCTAssertEqual(1, dogA.owners.count) XCTAssertEqual("Mike", dogA.owners[0].name) XCTAssertEqual(1, unaccessedDogB.owners.count) XCTAssertEqual("Andrea", unaccessedDogB.owners[0].name) } func testPassThreadSafeReferenceToAnyRealmCollection() { let realm = try! Realm() let company = SwiftCompanyObject() try! realm.write { realm.add(company) company.employees.append(SwiftEmployeeObject(value: ["name": "A"])) company.employees.append(SwiftEmployeeObject(value: ["name": "B"])) company.employees.append(SwiftEmployeeObject(value: ["name": "C"])) company.employees.append(SwiftEmployeeObject(value: ["name": "D"])) } let results = AnyRealmCollection(realm.objects(SwiftEmployeeObject.self) .filter("name != 'C'") .sorted(byKeyPath: "name", ascending: false)) let list = AnyRealmCollection(company.employees) XCTAssertEqual(3, results.count) XCTAssertEqual("D", results[0].name) XCTAssertEqual("B", results[1].name) XCTAssertEqual("A", results[2].name) XCTAssertEqual(4, list.count) XCTAssertEqual("A", list[0].name) XCTAssertEqual("B", list[1].name) XCTAssertEqual("C", list[2].name) XCTAssertEqual("D", list[3].name) let resultsRef = ThreadSafeReference(to: results) let listRef = ThreadSafeReference(to: list) dispatchSyncNewThread { let realm = try! Realm() let results = self.assertResolve(realm, resultsRef)! let list = self.assertResolve(realm, listRef)! XCTAssertEqual(3, results.count) XCTAssertEqual("D", results[0].name) XCTAssertEqual("B", results[1].name) XCTAssertEqual("A", results[2].name) XCTAssertEqual(4, list.count) XCTAssertEqual("A", list[0].name) XCTAssertEqual("B", list[1].name) XCTAssertEqual("C", list[2].name) XCTAssertEqual("D", list[3].name) } } }