mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-21 00:49:49 +01:00
experimental
This commit is contained in:
@@ -44,9 +44,14 @@ 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 })
|
||||||
}
|
}
|
||||||
@@ -58,6 +63,8 @@ class Person: CoreStoreObject {
|
|||||||
initial: "Mr.",
|
initial: "Mr.",
|
||||||
customSetter: Person.setTitle
|
customSetter: Person.setTitle
|
||||||
)
|
)
|
||||||
|
|
||||||
|
typealias AAA = Value
|
||||||
|
|
||||||
let name = Value.Required<String>(
|
let name = Value.Required<String>(
|
||||||
"name",
|
"name",
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -119,10 +120,32 @@ public enum ValueContainer<O: CoreStoreObject> {
|
|||||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||||
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
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user