experimental

This commit is contained in:
John Estropia
2019-09-05 18:40:39 +09:00
parent 12dc32f7e6
commit 2cdd85c49e
3 changed files with 119 additions and 24 deletions

View File

@@ -45,8 +45,13 @@ class Animal: CoreStoreObject {
class Dog: Animal { class Dog: Animal {
typealias VV = ValueContainer<Dog>
let nickname = Value.Optional<String>("nickname") let nickname = Value.Optional<String>("nickname")
let age = Value.Required<Int>("age", initial: 1)
@ValueContainer<Dog>.Required("age", initial: 1)
var age: Int
let friends = Relationship.ToManyOrdered<Dog>("friends") let friends = Relationship.ToManyOrdered<Dog>("friends")
let friendedBy = Relationship.ToManyUnordered<Dog>("friendedBy", inverse: { $0.friends }) let friendedBy = Relationship.ToManyUnordered<Dog>("friendedBy", inverse: { $0.friends })
} }
@@ -59,6 +64,8 @@ class Person: CoreStoreObject {
customSetter: Person.setTitle customSetter: Person.setTitle
) )
typealias AAA = Value
let name = Value.Required<String>( let name = Value.Required<String>(
"name", "name",
initial: "", initial: "",
@@ -168,17 +175,13 @@ class DynamicModelTests: BaseTestDataTestCase {
let dog = transaction.create(Into<Dog>()) let dog = transaction.create(Into<Dog>())
XCTAssertEqual(dog.species.value, "Swift") XCTAssertEqual(dog.species.value, "Swift")
XCTAssertEqual(dog.nickname.value, nil) XCTAssertEqual(dog.nickname.value, nil)
XCTAssertEqual(dog.age.value, 1) XCTAssertEqual(dog.age, 1)
#if swift(>=5.1) #if swift(>=5.1)
let dogKeyPathBuilder = Dog.keyPathBuilder() let dogKeyPathBuilder = Dog.keyPathBuilder()
XCTAssertEqual(dogKeyPathBuilder.species.keyPathString, "SELF.species") XCTAssertEqual(dogKeyPathBuilder.species.keyPathString, "SELF.species")
XCTAssertEqual(dogKeyPathBuilder.master.title.keyPathString, "SELF.master.title") 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") XCTAssertEqual(dogKeyPathBuilder.master.spouse.pets.color.keyPathString, "SELF.master.spouse.pets.color")
#endif #endif
@@ -299,40 +302,40 @@ class DynamicModelTests: BaseTestDataTestCase {
XCTAssertNotNil(person) XCTAssertNotNil(person)
XCTAssertEqual(person!.pets.value.first, dog) XCTAssertEqual(person!.pets.value.first, dog)
let p3 = Where<Dog>({ $0.age == 10 }) let p3 = Where<Dog>({ $0.$age == 10 })
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10)) XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
let totalAge = try transaction.queryValue(From<Dog>().select(Int.self, .sum(\Dog.age))) let totalAge = try transaction.queryValue(From<Dog>().select(Int.self, .sum(\Dog.$age)))
XCTAssertEqual(totalAge, 1) XCTAssertEqual(totalAge, 1)
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>() From<Dog>()
.where(\Animal.species == "Dog" && \Dog.age == 10) .where(\Animal.species == "Dog" && \Dog.$age == 10)
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>() From<Dog>()
.where(\Dog.age == 10 && \Animal.species == "Dog") .where(\Dog.$age == 10 && \Animal.species == "Dog")
.orderBy(.ascending({ $0.species })) .orderBy(.ascending({ $0.species }))
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>(), From<Dog>(),
Where<Dog>({ $0.age > 10 && $0.age <= 15 }) Where<Dog>({ $0.$age > 10 && $0.$age <= 15 })
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>(), From<Dog>(),
Where<Dog>({ $0.species == "Dog" && $0.age == 10 }) Where<Dog>({ $0.species == "Dog" && $0.$age == 10 })
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>(), From<Dog>(),
Where<Dog>({ $0.age == 10 && $0.species == "Dog" }) Where<Dog>({ $0.$age == 10 && $0.species == "Dog" })
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>(), From<Dog>(),
Where<Dog>({ $0.age > 10 && $0.age <= 15 }) Where<Dog>({ $0.$age > 10 && $0.$age <= 15 })
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>(), From<Dog>(),
(\Dog.age > 10 && \Dog.age <= 15) (\Dog.$age > 10 && \Dog.$age <= 15)
) )
}, },
success: { _ in success: { _ in

View File

@@ -131,11 +131,10 @@ extension ImportableUniqueObject where UniqueIDType.QueryableNativeType: CoreDat
} }
set { set {
self.cs_toRaw() self.cs_toRaw().setValue(
.setValue( newValue,
newValue, forKvcKey: self.runtimeType().uniqueIDKeyPath,
forKvcKey: self.runtimeType().uniqueIDKeyPath, willSetValue: { ($0.cs_toQueryableNativeType() as CoreDataNativeType) }
willSetValue: { ($0.cs_toQueryableNativeType() as CoreDataNativeType) }
) )
} }
} }

View File

@@ -73,6 +73,7 @@ public enum ValueContainer<O: CoreStoreObject> {
``` ```
- Important: `Value.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. - 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<V: ImportableAttributeType>: AttributeKeyPathStringConvertible, AttributeProtocol { public final class Required<V: ImportableAttributeType>: AttributeKeyPathStringConvertible, AttributeProtocol {
/** /**
@@ -110,7 +111,7 @@ public enum ValueContainer<O: CoreStoreObject> {
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject<O>`, 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<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively. - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject<O>`, 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<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.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:)`. - 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, _ keyPath: KeyPathString,
initial: @autoclosure @escaping () -> V, initial: @autoclosure @escaping () -> V,
isTransient: Bool = false, isTransient: Bool = false,
@@ -120,9 +121,31 @@ public enum ValueContainer<O: CoreStoreObject> {
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil, customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) { affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
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<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.keyPath = keyPath self.keyPath = keyPath
self.isTransient = isTransient self.isTransient = isTransient
self.defaultValue = { initial().cs_toQueryableNativeType() } self.defaultValue = { wrappedValue().cs_toQueryableNativeType() }
self.versionHashModifier = versionHashModifier self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter self.customGetter = customGetter
@@ -130,6 +153,76 @@ public enum ValueContainer<O: CoreStoreObject> {
self.affectedByKeyPaths = affectedByKeyPaths self.affectedByKeyPaths = affectedByKeyPaths
} }
@available(*, unavailable)
public var wrappedValue: V {
get { fatalError() }
set { fatalError() }
}
// @available(*, unavailable)
public var projectedValue: ValueContainer<O>.Required<V> {
get { return self }
set {}
}
public static subscript(
_enclosingInstance instance: O,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<O, V>,
storage storageKeyPath: ReferenceWritableKeyPath<O, ValueContainer<O>.Required<V>>
) -> 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<O>(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<O>(object), newValue)
}
return object.setValue(
newValue.cs_toQueryableNativeType(),
forKey: property.keyPath
)
}
}
}
/** /**
The attribute value The attribute value
*/ */