From e8eb309d8291a8b487ab52a0b3bcd0d463dae79f Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 20 Jun 2017 20:32:27 +0900 Subject: [PATCH] Added source docs on usage of custom getters and setters for CoreStoreObject properties (Value.Required, etc) --- Sources/PartialObject.swift | 5 +- Sources/Value.swift | 131 ++++++++++++++++++++++++------------ 2 files changed, 93 insertions(+), 43 deletions(-) diff --git a/Sources/PartialObject.swift b/Sources/PartialObject.swift index b103d21..49b0b2c 100644 --- a/Sources/PartialObject.swift +++ b/Sources/PartialObject.swift @@ -29,6 +29,9 @@ import Foundation // MARK: - PartialObject +/** + A `PartialObject` 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 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 heavy performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.persistentValue(for:)` instead of `PartialObject.value(for:)`, which would unintentionally execute the same closure again recursively. + */ public struct PartialObject { public func completeObject() -> O { @@ -37,7 +40,7 @@ public struct PartialObject { } - // MARK: Value.Required utilities + // MARK: Value.Required accessors/mutators public func value(for property: (O) -> ValueContainer.Required) -> V { diff --git a/Sources/Value.swift b/Sources/Value.swift index a16e160..c329ea4 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -93,12 +93,24 @@ public enum ValueContainer { ``` class Person: CoreStoreObject { let title = Value.Required("title", default: "Mr.") - let name = Value.Required( - "name", - customGetter: { (`self`, getValue) in - return "\(self.title.value) \(getValue())" - } + let name = Value.Required("name") + let displayName = Value.Required( + "displayName", + isTransient: true, + customGetter: Person.getName(_:) ) + + private static func getName(_ partialObject: PartialObject) -> String { + let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) + if !cachedDisplayName.isEmpty { + return cachedDisplayName + } + let title = partialObject.value(for: { $0.title }) + let name = partialObject.value(for: { $0.name }) + let displayName = "\(title) \(name)" + partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + return displayName + } } ``` - parameter keyPath: the permanent attribute name for this property. @@ -107,13 +119,8 @@ public enum ValueContainer { - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - 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 renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier 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 name. - - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. - - parameter self: the `CoreStoreObject` - - parameter getValue: the original getter for the property - - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. - - parameter setValue: the original setter for the property - - parameter finalNewValue: the transformed new value - - parameter originalNewValue: the original new value + - parameter customGetter: use this closure as an "override" for the default property getter. 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.primitiveValue(for:)` instead of `PartialObject.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`, 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( @@ -280,13 +287,24 @@ public enum ValueContainer { Initializes the metadata for the property. ``` class Person: CoreStoreObject { - let title = Value.Required("title", default: "Mr.") - let name = Value.Required( - "name", - customGetter: { (`self`, getValue) in - return "\(self.title.value) \(getValue())" - } + let title = Value.Optional("title", default: "Mr.") + let name = Value.Optional("name") + let displayName = Value.Optional( + "displayName", + isTransient: true, + customGetter: Person.getName(_:) ) + + private static func getName(_ partialObject: PartialObject) -> String? { + if let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) { + return cachedDisplayName + } + let title = partialObject.value(for: { $0.title }) + let name = partialObject.value(for: { $0.name }) + let displayName = "\(title) \(name)" + partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + return displayName + } } ``` - parameter keyPath: the permanent attribute name for this property. @@ -453,7 +471,8 @@ public extension ValueContainer.Required where V: EmptyableAttributeType { Initializes the metadata for the property. This convenience initializer uses the `EmptyableAttributeType`'s "empty" value as the initial value for the property when the object is first created (e.g. `false` for `Bool`, `0` for `Int`, `""` for `String`, etc.) ``` class Person: CoreStoreObject { - let title = Value.Required("title") // initial value defaults to empty string + let title = Value.Required("title", default: "Mr.") // explicit default value + let name = Value.Required("name") // initial value defaults to empty string } ``` - parameter keyPath: the permanent attribute name for this property. @@ -461,13 +480,8 @@ public extension ValueContainer.Required where V: EmptyableAttributeType { - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - 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 renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier 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 name. - - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. - - parameter self: the `CoreStoreObject` - - parameter getValue: the original getter for the property - - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. - - parameter setValue: the original setter for the property - - parameter finalNewValue: the transformed new value - - parameter originalNewValue: the original new value + - parameter customGetter: use this closure as an "override" for the default property getter. 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.primitiveValue(for:)` instead of `PartialObject.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`, 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 convenience init( @@ -528,7 +542,30 @@ public enum TransformableContainer { Initializes the metadata for the property. ``` class Animal: CoreStoreObject { - let color = Transformable.Optional("color") + let species = Value.Required("species") + let color = Transformable.Required( + "color", + default: UIColor.clear, + isTransient: true, + customGetter: Animal.getColor(_:) + ) + } + + private static func getColor(_ partialObject: PartialObject) -> UIColor { + let cachedColor = partialObject.primitiveValue(for: { $0.color }) + if cachedColor != UIColor.clear { + + return cachedColor + } + let color: UIColor + switch partialObject.value(for: { $0.species }) { + + case "Swift": color = UIColor.orange + case "Bulbasaur": color = UIColor.green + default: color = UIColor.black + } + partialObject.setPrimitiveValue(color, for: { $0.color }) + return color } ``` - parameter keyPath: the permanent attribute name for this property. @@ -537,13 +574,8 @@ public enum TransformableContainer { - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - 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 renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier 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 name. - - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. - - parameter self: the `CoreStoreObject` - - parameter getValue: the original getter for the property - - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. - - parameter setValue: the original setter for the property - - parameter finalNewValue: the transformed new value - - parameter originalNewValue: the original new value + - parameter customGetter: use this closure as an "override" for the default property getter. 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.primitiveValue(for:)` instead of `PartialObject.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`, 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( @@ -708,7 +740,27 @@ public enum TransformableContainer { Initializes the metadata for the property. ``` class Animal: CoreStoreObject { - let color = Transformable.Optional("color") + let species = Value.Required("species") + let color = Transformable.Optional( + "color", + isTransient: true, + customGetter: Animal.getColor(_:) + ) + } + + private static func getColor(_ partialObject: PartialObject) -> UIColor? { + if let cachedColor = partialObject.primitiveValue(for: { $0.color }) { + return cachedColor + } + let color: UIColor? + switch partialObject.value(for: { $0.species }) { + + case "Swift": color = UIColor.orange + case "Bulbasaur": color = UIColor.green + default: return nil + } + partialObject.setPrimitiveValue(color, for: { $0.color }) + return color } ``` - parameter keyPath: the permanent attribute name for this property. @@ -717,13 +769,8 @@ public enum TransformableContainer { - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - 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 renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier 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 name. - - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. - - parameter self: the `CoreStoreObject` - - parameter getValue: the original getter for the property - - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. - - parameter setValue: the original setter for the property - - parameter finalNewValue: the transformed new value - - parameter originalNewValue: the original new value + - parameter customGetter: use this closure as an "override" for the default property getter. 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.primitiveValue(for:)` instead of `PartialObject.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`, 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(