From b88ade92d66a876839a6561150d212cafee76300 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Fri, 24 Mar 2023 18:28:43 +0900 Subject: [PATCH] add tentative replacement methods for SR-13069 workarounds --- Sources/Field.Coded.swift | 305 +++++++++++++++++++++++++++++++++++++ Sources/Field.Stored.swift | 92 +++++++++++ 2 files changed, 397 insertions(+) diff --git a/Sources/Field.Coded.swift b/Sources/Field.Coded.swift index 4f8df54..f2933b3 100644 --- a/Sources/Field.Coded.swift +++ b/Sources/Field.Coded.swift @@ -56,6 +56,8 @@ extension FieldContainer { @propertyWrapper public struct Coded: AttributeKeyPathStringConvertible, FieldAttributeProtocol { +#if swift(<5.4) + /** Initializes the metadata for the property. ``` @@ -99,6 +101,52 @@ extension FieldContainer { affectedByKeyPaths: affectedByKeyPaths ) } +#else + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + + @Field.Coded("eyeColor", coder: FieldCoders.NSCoding.self) + var eyeColor: UIColor = .black + } + ``` + - Important: Any changes in the `coder` are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store. + - parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead. + - 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 coder: The `FieldCoderType` to be used for encoding and decoding the value + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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`, make sure to use `field.primitiveValue` instead of `field.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( + wrappedValue initial: @autoclosure @escaping () -> V, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder fieldCoderType: Coder.Type, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) where Coder.FieldStoredValue == V { + + self.init( + defaultValue: initial, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(fieldCoderType) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + +#endif /** Initializes the metadata for the property. @@ -144,6 +192,8 @@ extension FieldContainer { ) } +#if swift(<5.4) + /** Initializes the metadata for the property. ``` @@ -194,6 +244,60 @@ extension FieldContainer { ) } +#else + + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + + @Field.Coded( + "bloodType", + coder: { + encode: { $0.toData() }, + decode: { BloodType(fromData: $0) } + } + ) + var bloodType: BloodType = .unknown + } + ``` + - Important: Any changes in the encoder/decoder are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store. + - parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead. + - 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 coder: The closures to be used for encoding and decoding the value + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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`, make sure to use `field.primitiveValue` instead of `field.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( + wrappedValue initial: @autoclosure @escaping () -> V, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder: (encode: (V) -> Data?, decode: (Data?) -> V), + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + defaultValue: initial, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(tag: UUID(), encode: coder.encode, decode: coder.decode) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + +#endif + /** Initializes the metadata for the property. ``` @@ -505,6 +609,8 @@ extension FieldContainer { extension FieldContainer.Coded where V: FieldOptionalType { +#if swift(<5.4) + /** Initializes the metadata for the property. ``` @@ -548,6 +654,54 @@ extension FieldContainer.Coded where V: FieldOptionalType { affectedByKeyPaths: affectedByKeyPaths ) } + +#else + + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + + @Field.Coded("eyeColor", coder: FieldCoders.NSCoding.self) + var eyeColor: UIColor? = nil + } + ``` + - Important: Any changes in the `coder` are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store. + - parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead. + - 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 coder: The `FieldCoderType` to be used for encoding and decoding the value + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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`, make sure to use `field.primitiveValue` instead of `field.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( + wrappedValue initial: @autoclosure @escaping () -> V = nil, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder: Coder.Type, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) where Coder.FieldStoredValue == V.Wrapped { + + self.init( + defaultValue: { initial().cs_wrappedValue }, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(coder) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + +#endif /** Initializes the metadata for the property. @@ -593,6 +747,9 @@ extension FieldContainer.Coded where V: FieldOptionalType { ) } + +#if swift(<5.4) + /** Initializes the metadata for the property. ``` @@ -643,6 +800,60 @@ extension FieldContainer.Coded where V: FieldOptionalType { ) } +#else + + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + + @Field.Coded( + "bloodType", + coder: { + encode: { $0.toData() }, + decode: { BloodType(fromData: $0) } + } + ) + var bloodType: BloodType? + } + ``` + - Important: Any changes in the encoder/decoder are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store. + - parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead. + - 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 coder: The closures to be used for encoding and decoding the value + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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`, make sure to use `field.primitiveValue` instead of `field.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( + wrappedValue initial: @autoclosure @escaping () -> V = nil, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder: (encode: (V) -> Data?, decode: (Data?) -> V), + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + defaultValue: { initial().cs_wrappedValue }, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(tag: UUID(), encode: coder.encode, decode: coder.decode) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + +#endif + /** Initializes the metadata for the property. ``` @@ -700,6 +911,8 @@ extension FieldContainer.Coded where V: FieldOptionalType { extension FieldContainer.Coded where V: DefaultNSSecureCodable { +#if swift(<5.4) + /** Initializes the metadata for the property. This overload is for types supported by Core Data's default NSSecureCodable implementation: `NSArray`, `NSDictionary`, `NSSet`, `NSString`, `NSNumber`, `NSDate`, `NSData`, `NSURL`, `NSUUID`, and `NSNull`. ``` @@ -740,6 +953,51 @@ extension FieldContainer.Coded where V: DefaultNSSecureCodable { affectedByKeyPaths: affectedByKeyPaths ) } + +#else + + /** + Initializes the metadata for the property. This overload is for types supported by Core Data's default NSSecureCodable implementation: `NSArray`, `NSDictionary`, `NSSet`, `NSString`, `NSNumber`, `NSDate`, `NSData`, `NSURL`, `NSUUID`, and `NSNull`. + ``` + class Person: CoreStoreObject { + + @Field.Coded("customInfo") + var customInfo: NSDictionary = [:] + } + ``` + - parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead. + - 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 `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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`, make sure to use `field.primitiveValue` instead of `field.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( + wrappedValue initial: @autoclosure @escaping () -> V, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + defaultValue: initial, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding.self) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + +#endif /** Initializes the metadata for the property. This overload is for types supported by Core Data's default NSSecureCodable implementation: `NSArray`, `NSDictionary`, `NSSet`, `NSString`, `NSNumber`, `NSDate`, `NSData`, `NSURL`, `NSUUID`, and `NSNull`. @@ -788,6 +1046,8 @@ extension FieldContainer.Coded where V: DefaultNSSecureCodable { extension FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSSecureCodable { +#if swift(<5.4) + /** Initializes the metadata for the property. This overload is for types supported by Core Data's default NSSecureCodable implementation: `NSArray`, `NSDictionary`, `NSSet`, `NSString`, `NSNumber`, `NSDate`, `NSData`, `NSURL`, `NSUUID`, and `NSNull`. ``` @@ -828,6 +1088,51 @@ extension FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSS affectedByKeyPaths: affectedByKeyPaths ) } + +#else + + /** + Initializes the metadata for the property. This overload is for types supported by Core Data's default NSSecureCodable implementation: `NSArray`, `NSDictionary`, `NSSet`, `NSString`, `NSNumber`, `NSDate`, `NSData`, `NSURL`, `NSUUID`, and `NSNull`. + ``` + class Person: CoreStoreObject { + + @Field.Coded("customInfo") + var customInfo: NSDictionary? = nil + } + ``` + - parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead. + - 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 `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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`, make sure to use `field.primitiveValue` instead of `field.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( + wrappedValue initial: @autoclosure @escaping () -> V = nil, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + defaultValue: { initial().cs_wrappedValue }, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding.self) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + +#endif /** Initializes the metadata for the property. This overload is for types supported by Core Data's default NSSecureCodable implementation: `NSArray`, `NSDictionary`, `NSSet`, `NSString`, `NSNumber`, `NSDate`, `NSData`, `NSURL`, `NSUUID`, and `NSNull`. diff --git a/Sources/Field.Stored.swift b/Sources/Field.Stored.swift index 070f57b..cb46db0 100644 --- a/Sources/Field.Stored.swift +++ b/Sources/Field.Stored.swift @@ -50,6 +50,8 @@ extension FieldContainer { @propertyWrapper public struct Stored: AttributeKeyPathStringConvertible, FieldAttributeProtocol { +#if swift(<5.4) + /** Initializes the metadata for the property. ``` @@ -89,6 +91,50 @@ extension FieldContainer { affectedByKeyPaths: affectedByKeyPaths ) } + +#else + + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + + @Field.Stored("title") + var title: String = "Mr." + } + ``` + - parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead. + - 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 `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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`, make sure to use `field.primitiveValue` instead of `field.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( + wrappedValue initial: @autoclosure @escaping () -> V, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + wrappedValue: initial, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + +#endif /** Initializes the metadata for the property. @@ -368,6 +414,8 @@ extension FieldContainer { extension FieldContainer.Stored where V: FieldOptionalType { +#if swift(<5.4) + /** Initializes the metadata for the property. ``` @@ -407,6 +455,50 @@ extension FieldContainer.Stored where V: FieldOptionalType { affectedByKeyPaths: affectedByKeyPaths ) } + +#else + + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + + @Field.Stored("nickname") + var nickname: String? + } + ``` + - parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead. + - 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 `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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`, make sure to use `field.primitiveValue` instead of `field.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( + wrappedValue initial: @autoclosure @escaping () -> V = nil, + _ keyPath: KeyPathString = { fatalError("'keyPath' argument required (SR-13069 workaround)") }(), + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + wrappedValue: initial, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + +#endif /** Initializes the metadata for the property.