diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index e138e48..eb34eaa 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -40,19 +40,19 @@ class Dog: Animal { let nickname = Value.Optional("nickname") let age = Value.Required("age", default: 1) let friends = Relationship.ToManyOrdered("friends") - let friends2 = Relationship.ToManyUnordered("friends2", inverse: { $0.friends }) + let friendedBy = Relationship.ToManyUnordered("friendedBy", inverse: { $0.friends }) } class Person: CoreStoreObject { - + let title = Value.Required("title", default: "Mr.") let name = Value.Required( "name", customGetter: { (`self`, getValue) in - return "Mr. \(getValue())" + return "\(self.title.value) \(getValue())" } ) - let pet = Relationship.ToOne("pet", inverse: { $0.master }) + let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) } @@ -69,12 +69,12 @@ class DynamicModelTests: BaseTestDataTestCase { Entity("Animal"), Entity("Dog"), Entity("Person") - ], + ]/*, versionLock: [ "Animal": [0x2698c812ebbc3b97, 0x751e3fa3f04cf9, 0x51fd460d3babc82, 0x92b4ba735b5a3053], "Dog": [0x5285f8e3aff69199, 0x62c3291b59f2ec7c, 0xbe5a571397a4117b, 0x97fb40f5b79ffbdc], "Person": [0xae4060a59f990ef0, 0x8ac83a6e1411c130, 0xa29fea58e2e38ab6, 0x2071bb7e33d77887] - ] + ]*/ ) ) self.prepareStack(dataStack, configurations: [nil]) { (stack) in @@ -112,16 +112,20 @@ class DynamicModelTests: BaseTestDataTestCase { XCTAssertEqual(dog.nickname.value, "Spot") let person = transaction.create(Into()) - XCTAssertNil(person.pet.value) + XCTAssertTrue(person.pets.value.isEmpty) person.name .= "John" XCTAssertEqual(person.name.value, "Mr. John") // Custom getter - person.pet .= dog - XCTAssertEqual(person.pet.value, dog) - XCTAssertEqual(person.pet.value?.master.value, person) + person.title .= "Sir" + XCTAssertEqual(person.name.value, "Sir John") + + person.pets.value.insert(dog) + XCTAssertEqual(person.pets.count, 1) + XCTAssertEqual(person.pets.value.first, dog) + XCTAssertEqual(person.pets.value.first?.master.value, person) XCTAssertEqual(dog.master.value, person) - XCTAssertEqual(dog.master.value?.pet.value, dog) + XCTAssertEqual(dog.master.value?.pets.value.first, dog) }, success: { @@ -152,7 +156,7 @@ class DynamicModelTests: BaseTestDataTestCase { let person = transaction.fetchOne(From()) XCTAssertNotNil(person) - XCTAssertEqual(person!.pet.value, dog) + XCTAssertEqual(person!.pets.value.first, dog) let p3 = Dog.where({ $0.age == 10 }) XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10)) diff --git a/Sources/Relationship.swift b/Sources/Relationship.swift index 5071194..066cd72 100644 --- a/Sources/Relationship.swift +++ b/Sources/Relationship.swift @@ -150,8 +150,6 @@ public enum RelationshipContainer { public final class ToManyOrdered: RelationshipProtocol { - // MARK: - - public static func .= (_ relationship: RelationshipContainer.ToManyOrdered, _ value: [D]) { relationship.value = value @@ -187,45 +185,15 @@ public enum RelationshipContainer { self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - // TODO: add subscripts, indexed operations for more performant single updates - public var value: [D] { get { - let object = self.parentObject() as! O - CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { - - guard let orderedSet = $0 as! NSOrderedSet? else { - - return [] - } - return orderedSet.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) - } - ) + return self.nativeValue.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) } set { - let object = self.parentObject() as! O - CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - CoreStore.assert( - object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." - ) - object.rawObject!.setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { NSOrderedSet(array: $0.map({ $0.rawObject! })) } - ) + self.nativeValue = NSOrderedSet(array: newValue.map({ $0.rawObject! })) } } @@ -249,6 +217,38 @@ public enum RelationshipContainer { CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") } + internal var nativeValue: NSOrderedSet { + + get { + + let object = self.parentObject() as! O + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { ($0 as! NSOrderedSet?) ?? [] } + ) + } + set { + + let object = self.parentObject() as! O + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath + ) + } + } + // MARK: Private @@ -260,7 +260,7 @@ public enum RelationshipContainer { self.versionHashModifier = versionHashModifier self.renamingIdentifier = renamingIdentifier - let range = (max(0, minCount) ... maxCount) + let range = (Swift.max(0, minCount) ... maxCount) self.minCount = range.lowerBound self.maxCount = range.upperBound } @@ -313,10 +313,40 @@ public enum RelationshipContainer { self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - // TODO: add subscripts, indexed operations for more performant single updates - public var value: Set { + get { + + return Set(self.nativeValue.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })) + } + set { + + self.nativeValue = NSSet(array: newValue.map({ $0.rawObject! })) + } + } + + + // MARK: RelationshipProtocol + + public let keyPath: KeyPath + + internal let isToMany = true + internal let isOptional = true + internal let isOrdered = false + internal let deleteRule: NSDeleteRule + internal let minCount: Int + internal let maxCount: Int + internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) + internal let versionHashModifier: String? + internal let renamingIdentifier: String? + + internal var parentObject: () -> CoreStoreObject = { + + CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") + } + + internal var nativeValue: NSSet { + get { let object = self.parentObject() as! O @@ -326,14 +356,7 @@ public enum RelationshipContainer { ) return object.rawObject!.getValue( forKvcKey: self.keyPath, - didGetValue: { - - guard let set = $0 as! NSSet? else { - - return [] - } - return Set(set.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })) - } + didGetValue: { ($0 as! NSSet?) ?? [] } ) } set { @@ -349,33 +372,12 @@ public enum RelationshipContainer { ) object.rawObject!.setValue( newValue, - forKvcKey: self.keyPath, - willSetValue: { NSSet(array: $0.map({ $0.rawObject! })) } + forKvcKey: self.keyPath ) } } - // MARK: RelationshipProtocol - - public let keyPath: KeyPath - - internal let isToMany = true - internal let isOptional = true - internal let isOrdered = true - internal let deleteRule: NSDeleteRule - internal let minCount: Int - internal let maxCount: Int - internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) - internal let versionHashModifier: String? - internal let renamingIdentifier: String? - - internal var parentObject: () -> CoreStoreObject = { - - CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") - } - - // MARK: Private private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?) { @@ -386,7 +388,7 @@ public enum RelationshipContainer { self.versionHashModifier = versionHashModifier self.renamingIdentifier = renamingIdentifier - let range = (max(0, minCount) ... maxCount) + let range = (Swift.max(0, minCount) ... maxCount) self.minCount = range.lowerBound self.maxCount = range.upperBound } @@ -414,6 +416,74 @@ public enum RelationshipContainer { } +// MARK: RelationshipContainer.ToManyOrdered: RandomAccessCollection + +extension RelationshipContainer.ToManyOrdered: RandomAccessCollection { + + // MARK: Sequence + + public typealias Iterator = AnyIterator + + public func makeIterator() -> Iterator { + + let iterator = self.nativeValue.makeIterator() + return AnyIterator({ D.cs_fromRaw(object: iterator.next() as! NSManagedObject) }) + } + + + // MARK: Collection + + public typealias Index = Int + + public var startIndex: Index { + + return 0 + } + + public var endIndex: Index { + + return self.nativeValue.count + } + + public subscript(position: Index) -> Iterator.Element { + + return D.cs_fromRaw(object: self.nativeValue[position] as! NSManagedObject) + } + + public func index(after i: Index) -> Index { + + return i + 1 + } +} + + +// MARK: RelationshipContainer.ToManyUnordered: Sequence + +extension RelationshipContainer.ToManyUnordered: Sequence { + + public var count: Int { + + return self.nativeValue.count + } + + public var isEmpty: Bool { + + return self.nativeValue.count == 0 + } + + + // MARK: Sequence + + public typealias Iterator = AnyIterator + + public func makeIterator() -> Iterator { + + let iterator = self.nativeValue.makeIterator() + return AnyIterator({ D.cs_fromRaw(object: iterator.next() as! NSManagedObject) }) + } +} + + // MARK: - RelationshipProtocol internal protocol RelationshipProtocol: class {