improved API for custom getters and setters in Field properties

This commit is contained in:
John Estropia
2020-02-18 18:17:52 +09:00
parent 2d1b1e0592
commit 843adf21f7
6 changed files with 408 additions and 131 deletions

View File

@@ -65,15 +65,15 @@ extension FieldContainer {
@Field.Virtual("displayName", customGetter: Person.getName(_:))
var displayName: String = ""
private static func getName(_ partialObject: PartialObject<Person>) -> String {
let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName)
private static func getName(_ object: ObjectProxy<Person>) -> String {
let cachedDisplayName = object.primitiveValue(for: \.$displayName)
if !cachedDisplayName.isEmpty {
return cachedDisplayName
}
let title = partialObject.value(for: \.$title)
let name = partialObject.value(for: \.$name)
let title = object.value(for: \.$title)
let name = object.value(for: \.$name)
let displayName = "\(title) \(name)"
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
object.setPrimitiveValue(displayName, for: { $0.displayName })
return displayName
}
}
@@ -82,8 +82,8 @@ extension FieldContainer {
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter customGetter: use this closure as an "override" for the default property getter. 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>.primitiveValue(for:)` instead of `PartialObject<O>.value(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 customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.primitiveValue(for:)` instead of `ObjectProxy<O>.value(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 `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.$property.primitiveValue` instead of `ObjectProxy<O>.$property.value`, 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<Coder: FieldCoderType>(
@@ -92,8 +92,8 @@ extension FieldContainer {
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
coder fieldCoderType: Coder.Type,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
) where Coder.FieldStoredValue == V {
@@ -116,8 +116,8 @@ extension FieldContainer {
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
coder: (encode: (V) -> Data?, decode: (Data?) -> V),
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
) {
@@ -209,7 +209,10 @@ extension FieldContainer {
let field = field as! Self
if let customGetter = field.customGetter {
return customGetter(PartialObject<O>(rawObject))
return customGetter(
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field)
)
}
let keyPath = field.keyPath
switch rawObject.value(forKey: keyPath) {
@@ -237,7 +240,11 @@ extension FieldContainer {
let keyPath = field.keyPath
if let customSetter = field.customSetter {
return customSetter(PartialObject<O>(rawObject), newValue)
return customSetter(
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field),
newValue
)
}
return rawObject.setValue(newValue, forKey: keyPath)
}
@@ -279,7 +286,10 @@ extension FieldContainer {
rawObject.didAccessValue(forKey: keyPath)
}
let value = customGetter(PartialObject<O>(rawObject))
let value = customGetter(
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self)
)
return value
}
}
@@ -300,7 +310,8 @@ extension FieldContainer {
rawObject.didChangeValue(forKey: keyPath)
}
customSetter(
PartialObject<O>(rawObject),
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self),
newValue as! V
)
}
@@ -316,8 +327,8 @@ extension FieldContainer {
versionHashModifier: @escaping () -> String?,
renamingIdentifier: @escaping () -> String?,
valueTransformer: @escaping () -> Internals.AnyFieldCoder?,
customGetter: ((_ partialObject: PartialObject<O>) -> V)?,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? ,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? ,
affectedByKeyPaths: @escaping () -> Set<KeyPathString>) {
self.keyPath = keyPath
@@ -346,8 +357,8 @@ extension FieldContainer {
// MARK: Private
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
private let customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?
private let customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)?
}
}
@@ -369,15 +380,15 @@ extension FieldContainer.Coded where V: FieldOptionalType {
@Field.Virtual("displayName", customGetter: Person.getName(_:))
var displayName: String = ""
private static func getName(_ partialObject: PartialObject<Person>) -> String {
let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName)
private static func getName(_ object: ObjectProxy<Person>) -> String {
let cachedDisplayName = object.primitiveValue(for: \.$displayName)
if !cachedDisplayName.isEmpty {
return cachedDisplayName
}
let title = partialObject.value(for: \.$title)
let name = partialObject.value(for: \.$name)
let title = object.value(for: \.$title)
let name = object.value(for: \.$name)
let displayName = "\(title) \(name)"
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
object.setPrimitiveValue(displayName, for: { $0.displayName })
return displayName
}
}
@@ -386,8 +397,8 @@ extension FieldContainer.Coded where V: FieldOptionalType {
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter customGetter: use this closure as an "override" for the default property getter. 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>.primitiveValue(for:)` instead of `PartialObject<O>.value(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 customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.primitiveValue(for:)` instead of `ObjectProxy<O>.value(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 `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.$property.primitiveValue` instead of `ObjectProxy<O>.$property.value`, 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<Coder: FieldCoderType>(
@@ -396,8 +407,8 @@ extension FieldContainer.Coded where V: FieldOptionalType {
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
coder: Coder.Type,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
) where Coder.FieldStoredValue == V.Wrapped {
@@ -420,8 +431,8 @@ extension FieldContainer.Coded where V: FieldOptionalType {
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
coder: (encode: (V) -> Data?, decode: (Data?) -> V),
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
) {
@@ -457,15 +468,15 @@ extension FieldContainer.Coded where V: DefaultNSSecureCodable {
@Field.Virtual("displayName", customGetter: Person.getName(_:))
var displayName: String = ""
private static func getName(_ partialObject: PartialObject<Person>) -> String {
let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName)
private static func getName(_ object: ObjectProxy<Person>) -> String {
let cachedDisplayName = object.primitiveValue(for: \.$displayName)
if !cachedDisplayName.isEmpty {
return cachedDisplayName
}
let title = partialObject.value(for: \.$title)
let name = partialObject.value(for: \.$name)
let title = object.value(for: \.$title)
let name = object.value(for: \.$name)
let displayName = "\(title) \(name)"
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
object.setPrimitiveValue(displayName, for: { $0.displayName })
return displayName
}
}
@@ -474,8 +485,8 @@ extension FieldContainer.Coded where V: DefaultNSSecureCodable {
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter customGetter: use this closure as an "override" for the default property getter. 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>.primitiveValue(for:)` instead of `PartialObject<O>.value(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 customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.primitiveValue(for:)` instead of `ObjectProxy<O>.value(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 `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.$property.primitiveValue` instead of `ObjectProxy<O>.$property.value`, 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(
@@ -483,8 +494,8 @@ extension FieldContainer.Coded where V: DefaultNSSecureCodable {
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
) {
@@ -512,8 +523,8 @@ extension FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSS
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
) {

View File

@@ -62,28 +62,26 @@ extension FieldContainer {
@Field.Stored("name")
var name: String = ""
@Field.Virtual("displayName", customGetter: Person.getName(_:))
var displayName: String = ""
private static func getName(_ partialObject: PartialObject<Person>) -> String {
let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName)
if !cachedDisplayName.isEmpty {
return cachedDisplayName
@Field.Virtual(
"displayName",
customGetter: { (object, field) in
if let cached = field.primitiveValue, !cached.isEmpty {
return cached
}
let value = "\(object.$title.value) \(object.$name.value)"
field.primitiveValue = value
return value
}
let title = partialObject.value(for: \.$title)
let name = partialObject.value(for: \.$name)
let displayName = "\(title) \(name)"
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
return displayName
}
)
var displayName: String = ""
}
```
- parameter initial: the initial value for the property when the object is first create
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter customGetter: use this closure as an "override" for the default property getter. 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>.primitiveValue(for:)` instead of `PartialObject<O>.value(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 customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.primitiveValue(for:)` instead of `ObjectProxy<O>.value(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 `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.$property.primitiveValue` instead of `ObjectProxy<O>.$property.value`, 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(
@@ -91,8 +89,8 @@ extension FieldContainer {
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
) {
@@ -183,7 +181,10 @@ extension FieldContainer {
let field = field as! Self
if let customGetter = field.customGetter {
return customGetter(PartialObject<O>(rawObject))
return customGetter(
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field)
)
}
let keyPath = field.keyPath
switch rawObject.value(forKey: keyPath) {
@@ -211,7 +212,11 @@ extension FieldContainer {
let keyPath = field.keyPath
if let customSetter = field.customSetter {
return customSetter(PartialObject<O>(rawObject), newValue)
return customSetter(
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field),
newValue
)
}
return rawObject.setValue(
newValue.cs_toFieldStoredNativeType(),
@@ -239,7 +244,10 @@ extension FieldContainer {
rawObject.didAccessValue(forKey: keyPath)
}
let value = customGetter(PartialObject<O>(rawObject))
let value = customGetter(
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self)
)
return value.cs_toFieldStoredNativeType()
}
}
@@ -260,7 +268,8 @@ extension FieldContainer {
rawObject.didChangeValue(forKey: keyPath)
}
customSetter(
PartialObject<O>(rawObject),
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self),
V.cs_fromFieldStoredNativeType(newValue as! V.FieldStoredNativeType)
)
}
@@ -275,8 +284,8 @@ extension FieldContainer {
isOptional: Bool,
versionHashModifier: @escaping () -> String?,
renamingIdentifier: @escaping () -> String?,
customGetter: ((_ partialObject: PartialObject<O>) -> V)?,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? ,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? ,
affectedByKeyPaths: @escaping () -> Set<KeyPathString>) {
self.keyPath = keyPath
@@ -300,8 +309,8 @@ extension FieldContainer {
// MARK: Private
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
private let customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?
private let customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)?
}
}
@@ -323,15 +332,15 @@ extension FieldContainer.Stored where V: FieldOptionalType {
@Field.Virtual("displayName", customGetter: Person.getName(_:))
var displayName: String = ""
private static func getName(_ partialObject: PartialObject<Person>) -> String {
let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName)
private static func getName(_ object: ObjectProxy<Person>) -> String {
let cachedDisplayName = object.primitiveValue(for: \.$displayName)
if !cachedDisplayName.isEmpty {
return cachedDisplayName
}
let title = partialObject.value(for: \.$title)
let name = partialObject.value(for: \.$name)
let title = object.value(for: \.$title)
let name = object.value(for: \.$name)
let displayName = "\(title) \(name)"
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
object.setPrimitiveValue(displayName, for: { $0.displayName })
return displayName
}
}
@@ -340,8 +349,8 @@ extension FieldContainer.Stored where V: FieldOptionalType {
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter customGetter: use this closure as an "override" for the default property getter. 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>.primitiveValue(for:)` instead of `PartialObject<O>.value(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 customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.primitiveValue(for:)` instead of `ObjectProxy<O>.value(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 `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.$property.primitiveValue` instead of `ObjectProxy<O>.$property.value`, 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(
@@ -349,8 +358,8 @@ extension FieldContainer.Stored where V: FieldOptionalType {
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
self.init(

View File

@@ -65,28 +65,28 @@ extension FieldContainer {
@Field.Virtual("displayName", customGetter: Person.getName(_:))
var displayName: String = ""
private static func getName(_ partialObject: PartialObject<Person>) -> String {
let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName)
private static func getName(_ object: ObjectProxy<Person>) -> String {
let cachedDisplayName = object.primitiveValue(for: \.$displayName)
if !cachedDisplayName.isEmpty {
return cachedDisplayName
}
let title = partialObject.value(for: \.$title)
let name = partialObject.value(for: \.$name)
let title = object.value(for: \.$title)
let name = object.value(for: \.$name)
let displayName = "\(title) \(name)"
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
object.setPrimitiveValue(displayName, for: { $0.displayName })
return displayName
}
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter customGetter: use this closure as an "override" for the default property getter. 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>.primitiveValue(for:)` instead of `PartialObject<O>.value(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 customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.primitiveValue(for:)` instead of `ObjectProxy<O>.value(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 `ObjectProxy<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 `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.$property.primitiveValue` instead of `ObjectProxy<O>.$property.value`, 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(
_ keyPath: KeyPathString,
customGetter: @escaping (_ partialObject: PartialObject<O>) -> V,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: @escaping (_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
self.init(
@@ -106,8 +106,8 @@ extension FieldContainer {
wrappedValue initial: @autoclosure @escaping () -> V,
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
fatalError()
@@ -188,7 +188,10 @@ extension FieldContainer {
let field = field as! Self
if let customGetter = field.customGetter {
return customGetter(PartialObject<O>(rawObject))
return customGetter(
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field)
)
}
let keyPath = field.keyPath
switch rawObject.value(forKey: keyPath) {
@@ -216,7 +219,11 @@ extension FieldContainer {
let keyPath = field.keyPath
if let customSetter = field.customSetter {
return customSetter(PartialObject<O>(rawObject), newValue)
return customSetter(
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field),
newValue
)
}
return rawObject.setValue(newValue, forKey: keyPath)
}
@@ -241,7 +248,10 @@ extension FieldContainer {
rawObject.didAccessValue(forKey: keyPath)
}
return customGetter(PartialObject<O>(rawObject))
return customGetter(
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self)
)
}
}
@@ -260,7 +270,11 @@ extension FieldContainer {
rawObject.didChangeValue(forKey: keyPath)
}
return customSetter(PartialObject<O>(rawObject), newValue as! V)
return customSetter(
ObjectProxy<O>(rawObject),
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self),
newValue as! V
)
}
}
@@ -270,8 +284,8 @@ extension FieldContainer {
fileprivate init(
keyPath: KeyPathString,
isOptional: Bool,
customGetter: ((_ partialObject: PartialObject<O>) -> V)?,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? ,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? ,
affectedByKeyPaths: @escaping () -> Set<KeyPathString>) {
self.keyPath = keyPath
@@ -295,8 +309,8 @@ extension FieldContainer {
// MARK: Private
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
private let customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?
private let customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)?
}
}
@@ -305,8 +319,8 @@ extension FieldContainer.Virtual where V: FieldOptionalType {
public init(
_ keyPath: KeyPathString,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
self.init(

235
Sources/ObjectProxy.swift Normal file
View File

@@ -0,0 +1,235 @@
//
// ObjectProxy.swift
// CoreStore
//
// Copyright © 2020 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - ObjectProxy
/**
An `ObjectProxy` is only used when overriding getters and setters for `CoreStoreObject` properties. Custom getters and setters are implemented as a closure that "overrides" the default property getter/setter. The closure receives an `ObjectProxy<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 every time KVO invokes this accessor method incurs a heavy performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.$property.primitiveValue` instead of `ObjectProxy<O>.$property.value`, which would execute the same accessor again recursively.
*/
@dynamicMemberLookup
public struct ObjectProxy<O: CoreStoreObject> {
/**
Returns the value for the property identified by a given key.
*/
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Stored<V>>) -> FieldProxy<V> {
return .init(rawObject: self.rawObject, keyPath: member)
}
/**
Returns the value for the property identified by a given key.
*/
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Virtual<V>>) -> FieldProxy<V> {
return .init(rawObject: self.rawObject, keyPath: member)
}
/**
Returns the value for the property identified by a given key.
*/
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Coded<V>>) -> FieldProxy<V> {
return .init(rawObject: self.rawObject, keyPath: member)
}
// MARK: Internal
internal let rawObject: CoreStoreManagedObject
internal init(_ rawObject: CoreStoreManagedObject) {
self.rawObject = rawObject
}
// MARK: - FieldProxy
public struct FieldProxy<V> {
// MARK: Public
/**
Returns the value for the specified property from the objects private internal storage.
Accessing this property does not invoke the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). This method is used primarily to implement custom accessor methods that need direct access to the object's private storage.
*/
public var primitiveValue: V? {
get {
return self.getPrimitiveValue()
}
nonmutating set {
self.setPrimitiveValue(newValue)
}
}
/**
Returns the value for the property identified by a given key.
Accessing this property triggers the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`).
*/
public var value: V {
get {
return self.getValue()
}
nonmutating set {
self.setValue(newValue)
}
}
// MARK: Internal
internal init<OBase>(rawObject: CoreStoreManagedObject, keyPath: KeyPath<O, FieldContainer<OBase>.Stored<V>>) {
self.init(rawObject: rawObject, field: O.meta[keyPath: keyPath])
}
internal init<OBase>(rawObject: CoreStoreManagedObject, keyPath: KeyPath<O, FieldContainer<OBase>.Virtual<V>>) {
self.init(rawObject: rawObject, field: O.meta[keyPath: keyPath])
}
internal init<OBase>(rawObject: CoreStoreManagedObject, keyPath: KeyPath<O, FieldContainer<OBase>.Coded<V>>) {
self.init(rawObject: rawObject, field: O.meta[keyPath: keyPath])
}
internal init<OBase>(rawObject: CoreStoreManagedObject, field: FieldContainer<OBase>.Stored<V>) {
let keyPathString = field.keyPath
self.getValue = {
return FieldContainer<O>.Stored<V>.read(field: field, for: rawObject) as! V
}
self.setValue = {
FieldContainer<O>.Stored<V>.modify(field: field, for: rawObject, newValue: $0)
}
self.getPrimitiveValue = {
return V.cs_fromFieldStoredNativeType(
rawObject.primitiveValue(forKey: keyPathString) as! V.FieldStoredNativeType
)
}
self.setPrimitiveValue = {
rawObject.setPrimitiveValue(
$0.cs_toFieldStoredNativeType(),
forKey: keyPathString
)
}
}
internal init<OBase>(rawObject: CoreStoreManagedObject, field: FieldContainer<OBase>.Virtual<V>) {
let keyPathString = field.keyPath
self.getValue = {
return FieldContainer<O>.Virtual<V>.read(field: field, for: rawObject) as! V
}
self.setValue = {
FieldContainer<O>.Virtual<V>.modify(field: field, for: rawObject, newValue: $0)
}
self.getPrimitiveValue = {
switch rawObject.primitiveValue(forKey: keyPathString) {
case let value as V:
return value
case nil,
is NSNull,
_? /* any other unrelated type */ :
return nil
}
}
self.setPrimitiveValue = {
rawObject.setPrimitiveValue(
$0,
forKey: keyPathString
)
}
}
internal init<OBase>(rawObject: CoreStoreManagedObject, field: FieldContainer<OBase>.Coded<V>) {
let keyPathString = field.keyPath
self.getValue = {
return FieldContainer<O>.Coded<V>.read(field: field, for: rawObject) as! V
}
self.setValue = {
FieldContainer<O>.Coded<V>.modify(field: field, for: rawObject, newValue: $0)
}
self.getPrimitiveValue = {
switch rawObject.primitiveValue(forKey: keyPathString) {
case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox:
rawObject.setPrimitiveValue(valueBox.value, forKey: keyPathString)
return valueBox.value as? V
case let value as V:
return value
case nil,
is NSNull,
_? /* any other unrelated type */ :
return nil
}
}
self.setPrimitiveValue = {
rawObject.setPrimitiveValue(
$0,
forKey: keyPathString
)
}
}
// MARK: Private
private let getValue: () -> V
private let setValue: (V) -> Void
private let getPrimitiveValue: () -> V?
private let setPrimitiveValue: (V?) -> Void
}
}