From 2cdd85c49e60499ea07fde27554db3fdd6e15bec Mon Sep 17 00:00:00 2001 From: John Estropia Date: Thu, 5 Sep 2019 18:40:39 +0900 Subject: [PATCH] experimental --- CoreStoreTests/DynamicModelTests.swift | 35 ++++----- Sources/ImportableUniqueObject.swift | 9 ++- Sources/Value.swift | 99 +++++++++++++++++++++++++- 3 files changed, 119 insertions(+), 24 deletions(-) diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index 2283795..c863b44 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -44,9 +44,14 @@ class Animal: CoreStoreObject { } class Dog: Animal { - + + typealias VV = ValueContainer let nickname = Value.Optional("nickname") - let age = Value.Required("age", initial: 1) + + + @ValueContainer.Required("age", initial: 1) + var age: Int + let friends = Relationship.ToManyOrdered("friends") let friendedBy = Relationship.ToManyUnordered("friendedBy", inverse: { $0.friends }) } @@ -58,6 +63,8 @@ class Person: CoreStoreObject { initial: "Mr.", customSetter: Person.setTitle ) + + typealias AAA = Value let name = Value.Required( "name", @@ -168,17 +175,13 @@ class DynamicModelTests: BaseTestDataTestCase { let dog = transaction.create(Into()) XCTAssertEqual(dog.species.value, "Swift") XCTAssertEqual(dog.nickname.value, nil) - XCTAssertEqual(dog.age.value, 1) + XCTAssertEqual(dog.age, 1) #if swift(>=5.1) let dogKeyPathBuilder = Dog.keyPathBuilder() XCTAssertEqual(dogKeyPathBuilder.species.keyPathString, "SELF.species") XCTAssertEqual(dogKeyPathBuilder.master.title.keyPathString, "SELF.master.title") - let a = dogKeyPathBuilder.master - let b = dogKeyPathBuilder.master.spouse - let c = dogKeyPathBuilder.master.spouse.pets - let d = dogKeyPathBuilder.master.spouse.pets.color XCTAssertEqual(dogKeyPathBuilder.master.spouse.pets.color.keyPathString, "SELF.master.spouse.pets.color") #endif @@ -299,40 +302,40 @@ class DynamicModelTests: BaseTestDataTestCase { XCTAssertNotNil(person) XCTAssertEqual(person!.pets.value.first, dog) - let p3 = Where({ $0.age == 10 }) + let p3 = Where({ $0.$age == 10 }) XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10)) - let totalAge = try transaction.queryValue(From().select(Int.self, .sum(\Dog.age))) + let totalAge = try transaction.queryValue(From().select(Int.self, .sum(\Dog.$age))) XCTAssertEqual(totalAge, 1) _ = try transaction.fetchAll( From() - .where(\Animal.species == "Dog" && \Dog.age == 10) + .where(\Animal.species == "Dog" && \Dog.$age == 10) ) _ = try transaction.fetchAll( From() - .where(\Dog.age == 10 && \Animal.species == "Dog") + .where(\Dog.$age == 10 && \Animal.species == "Dog") .orderBy(.ascending({ $0.species })) ) _ = try transaction.fetchAll( From(), - Where({ $0.age > 10 && $0.age <= 15 }) + Where({ $0.$age > 10 && $0.$age <= 15 }) ) _ = try transaction.fetchAll( From(), - Where({ $0.species == "Dog" && $0.age == 10 }) + Where({ $0.species == "Dog" && $0.$age == 10 }) ) _ = try transaction.fetchAll( From(), - Where({ $0.age == 10 && $0.species == "Dog" }) + Where({ $0.$age == 10 && $0.species == "Dog" }) ) _ = try transaction.fetchAll( From(), - Where({ $0.age > 10 && $0.age <= 15 }) + Where({ $0.$age > 10 && $0.$age <= 15 }) ) _ = try transaction.fetchAll( From(), - (\Dog.age > 10 && \Dog.age <= 15) + (\Dog.$age > 10 && \Dog.$age <= 15) ) }, success: { _ in diff --git a/Sources/ImportableUniqueObject.swift b/Sources/ImportableUniqueObject.swift index e0bf5c9..ade09b8 100644 --- a/Sources/ImportableUniqueObject.swift +++ b/Sources/ImportableUniqueObject.swift @@ -131,11 +131,10 @@ extension ImportableUniqueObject where UniqueIDType.QueryableNativeType: CoreDat } set { - self.cs_toRaw() - .setValue( - newValue, - forKvcKey: self.runtimeType().uniqueIDKeyPath, - willSetValue: { ($0.cs_toQueryableNativeType() as CoreDataNativeType) } + self.cs_toRaw().setValue( + newValue, + forKvcKey: self.runtimeType().uniqueIDKeyPath, + willSetValue: { ($0.cs_toQueryableNativeType() as CoreDataNativeType) } ) } } diff --git a/Sources/Value.swift b/Sources/Value.swift index 73be9f6..9207bdb 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -73,6 +73,7 @@ public enum ValueContainer { ``` - Important: `Value.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. */ + @propertyWrapper public final class Required: AttributeKeyPathStringConvertible, AttributeProtocol { /** @@ -110,7 +111,7 @@ public enum ValueContainer { - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.setValue(_:for:)`, which would unintentionally execute the same closure again recursively. - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public init( + public convenience init( _ keyPath: KeyPathString, initial: @autoclosure @escaping () -> V, isTransient: Bool = false, @@ -119,10 +120,32 @@ public enum ValueContainer { customGetter: ((_ partialObject: PartialObject) -> V)? = nil, customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - + + self.init( + wrappedValue: initial(), + field: keyPath, + isTransient: isTransient, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + customGetter: customGetter, + customSetter: customSetter, + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + public init( + wrappedValue: @autoclosure @escaping () -> V, + field keyPath: KeyPathString, + isTransient: Bool = false, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + self.keyPath = keyPath self.isTransient = isTransient - self.defaultValue = { initial().cs_toQueryableNativeType() } + self.defaultValue = { wrappedValue().cs_toQueryableNativeType() } self.versionHashModifier = versionHashModifier self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter @@ -130,6 +153,76 @@ public enum ValueContainer { self.affectedByKeyPaths = affectedByKeyPaths } + @available(*, unavailable) + public var wrappedValue: V { + + get { fatalError() } + set { fatalError() } + } + +// @available(*, unavailable) + public var projectedValue: ValueContainer.Required { + + get { return self } + set {} + } + + public static subscript( + _enclosingInstance instance: O, + wrapped wrappedKeyPath: ReferenceWritableKeyPath, + storage storageKeyPath: ReferenceWritableKeyPath.Required> + ) -> ReturnValueType { + get { + + Internals.assert( + instance.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(instance.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + let property = instance[keyPath: storageKeyPath] + if let customGetter = property.customGetter { + + return customGetter(PartialObject(object)) + } + return V.cs_fromQueryableNativeType( + object.value(forKey: property.keyPath)! as! V.QueryableNativeType + )! + } + } + set { + + Internals.assert( + instance.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(instance.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + Internals.assert( + object.isEditableInContext() == true, + "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." + ) + let property = instance[keyPath: storageKeyPath] + if let customSetter = property.customSetter { + + return customSetter(PartialObject(object), newValue) + } + return object.setValue( + newValue.cs_toQueryableNativeType(), + forKey: property.keyPath + ) + } + } + } + /** The attribute value */