mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-15 13:43:43 +01:00
Field.Coded implementations for transformable attributes
This commit is contained in:
@@ -209,20 +209,23 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
let rawModel = NSManagedObjectModel()
|
||||
var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
|
||||
var allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:]
|
||||
var allFieldCoders: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:]
|
||||
for entity in self.allEntities {
|
||||
|
||||
let (entityDescription, customGetterSetterByKeyPaths) = self.entityDescription(
|
||||
let (entityDescription, customGetterSetterByKeyPaths, fieldCoders) = self.entityDescription(
|
||||
for: entity,
|
||||
initializer: CoreStoreSchema.firstPassCreateEntityDescription(from:in:)
|
||||
)
|
||||
entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription)
|
||||
allCustomGettersSetters[entity] = customGetterSetterByKeyPaths
|
||||
allFieldCoders[entity] = fieldCoders
|
||||
}
|
||||
CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity)
|
||||
CoreStoreSchema.thirdPassConnectInheritanceTreeAndIndexes(for: entityDescriptionsByEntity)
|
||||
CoreStoreSchema.fourthPassSynthesizeManagedObjectClasses(
|
||||
for: entityDescriptionsByEntity,
|
||||
allCustomGettersSetters: allCustomGettersSetters
|
||||
allCustomGettersSetters: allCustomGettersSetters,
|
||||
allFieldCoders: allFieldCoders
|
||||
)
|
||||
|
||||
rawModel.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! })
|
||||
@@ -254,22 +257,46 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
|
||||
private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
|
||||
private var customGettersSettersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:]
|
||||
private var fieldCodersByEntity: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:]
|
||||
private weak var cachedRawModel: NSManagedObjectModel?
|
||||
|
||||
private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity, ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter])) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]) {
|
||||
private func entityDescription(
|
||||
for entity: DynamicEntity,
|
||||
initializer: (DynamicEntity, ModelVersion) -> (
|
||||
entity: NSEntityDescription,
|
||||
customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter],
|
||||
fieldCoders: [KeyPathString: Internals.AnyFieldCoder]
|
||||
)
|
||||
) -> (
|
||||
entity: NSEntityDescription,
|
||||
customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter],
|
||||
fieldCoders: [KeyPathString: Internals.AnyFieldCoder]
|
||||
) {
|
||||
|
||||
if let cachedEntityDescription = self.entityDescriptionsByEntity[entity] {
|
||||
|
||||
return (cachedEntityDescription, self.customGettersSettersByEntity[entity] ?? [:])
|
||||
return (
|
||||
cachedEntityDescription,
|
||||
self.customGettersSettersByEntity[entity] ?? [:],
|
||||
self.fieldCodersByEntity[entity] ?? [:]
|
||||
)
|
||||
}
|
||||
let modelVersion = self.modelVersion
|
||||
let (entityDescription, customGetterSetterByKeyPaths) = withoutActuallyEscaping(initializer, do: { $0(entity, modelVersion) })
|
||||
let (entityDescription, customGetterSetterByKeyPaths, fieldCoders) = withoutActuallyEscaping(
|
||||
initializer,
|
||||
do: { $0(entity, modelVersion) }
|
||||
)
|
||||
self.entityDescriptionsByEntity[entity] = entityDescription
|
||||
self.customGettersSettersByEntity[entity] = customGetterSetterByKeyPaths
|
||||
return (entityDescription, customGetterSetterByKeyPaths)
|
||||
self.fieldCodersByEntity[entity] = fieldCoders
|
||||
return (entityDescription, customGetterSetterByKeyPaths, fieldCoders)
|
||||
}
|
||||
|
||||
private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]) {
|
||||
|
||||
private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> (
|
||||
entity: NSEntityDescription,
|
||||
customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter],
|
||||
fieldCoders: [KeyPathString: Internals.AnyFieldCoder]
|
||||
) {
|
||||
|
||||
let entityDescription = NSEntityDescription()
|
||||
entityDescription.coreStoreEntity = entity
|
||||
@@ -280,6 +307,7 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
|
||||
var keyPathsByAffectedKeyPaths: [KeyPathString: Set<KeyPathString>] = [:]
|
||||
var customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter] = [:]
|
||||
var fieldCoders: [KeyPathString: Internals.AnyFieldCoder] = [:]
|
||||
func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] {
|
||||
|
||||
var propertyDescriptions: [NSPropertyDescription] = []
|
||||
@@ -302,9 +330,15 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
description.allowsExternalBinaryDataStorage = entityDescriptionValues.allowsExternalBinaryDataStorage
|
||||
description.versionHashModifier = entityDescriptionValues.versionHashModifier
|
||||
description.renamingIdentifier = entityDescriptionValues.renamingIdentifier
|
||||
|
||||
let valueTransformer = entityDescriptionValues.valueTransformer
|
||||
description.valueTransformerName = valueTransformer?.transformerName.rawValue
|
||||
|
||||
propertyDescriptions.append(description)
|
||||
|
||||
keyPathsByAffectedKeyPaths[attribute.keyPath] = entityDescriptionValues.affectedByKeyPaths
|
||||
customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter)
|
||||
fieldCoders[attribute.keyPath] = valueTransformer
|
||||
|
||||
case let attribute as AttributeProtocol:
|
||||
Internals.assert(
|
||||
@@ -350,7 +384,7 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
}
|
||||
entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type)
|
||||
entityDescription.keyPathsByAffectedKeyPaths = keyPathsByAffectedKeyPaths
|
||||
return (entityDescription, customGetterSetterByKeyPaths)
|
||||
return (entityDescription, customGetterSetterByKeyPaths, fieldCoders)
|
||||
}
|
||||
|
||||
private static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) {
|
||||
@@ -523,7 +557,11 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
}
|
||||
}
|
||||
|
||||
private static func fourthPassSynthesizeManagedObjectClasses(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription], allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]]) {
|
||||
private static func fourthPassSynthesizeManagedObjectClasses(
|
||||
for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription],
|
||||
allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]],
|
||||
allFieldCoders: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]]
|
||||
) {
|
||||
|
||||
func createManagedObjectSubclass(for entityDescription: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]?) {
|
||||
|
||||
@@ -636,5 +674,13 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
customGetterSetterByKeyPaths: allCustomGettersSetters[dynamicEntity]
|
||||
)
|
||||
}
|
||||
|
||||
_ = allFieldCoders
|
||||
.flatMap({ (_, values) in values })
|
||||
.reduce(
|
||||
into: [:] as [NSValueTransformerName: Internals.AnyFieldCoder],
|
||||
{ (result, element) in result[element.value.transformerName] = element.value }
|
||||
)
|
||||
.forEach({ (_, fieldCoder) in fieldCoder.register() })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +177,9 @@ extension DynamicSchema {
|
||||
|
||||
defaultString = ", initial: /* required */"
|
||||
}
|
||||
case .undefinedAttributeType:
|
||||
#warning("TODO: Field.Computed")
|
||||
continue
|
||||
default:
|
||||
fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)")
|
||||
}
|
||||
|
||||
532
Sources/Field.Coded.swift
Normal file
532
Sources/Field.Coded.swift
Normal file
@@ -0,0 +1,532 @@
|
||||
//
|
||||
// Field.Coded.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: - FieldContainer
|
||||
|
||||
extension FieldContainer {
|
||||
|
||||
// MARK: - Coded
|
||||
|
||||
/**
|
||||
The containing type for stored property values. Any type that conforms to `FieldStorableType` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
@Field.Stored("species")
|
||||
var species = ""
|
||||
|
||||
@Field.Computed("pluralName", customGetter: Animal.pluralName(_:))
|
||||
var pluralName: String = ""
|
||||
|
||||
@Field.PlistCoded("color")
|
||||
var color: UIColor?
|
||||
}
|
||||
```
|
||||
- Important: `Field` properties are required to be used as `@propertyWrapper`s. Any other declaration not using the `@Field.Stored(...) var` syntax will be ignored.
|
||||
*/
|
||||
@propertyWrapper
|
||||
public struct Coded<V>: AttributeKeyPathStringConvertible, FieldAttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
@Field.Stored("title")
|
||||
var title: String = "Mr."
|
||||
|
||||
@Field.Stored("name")
|
||||
var name: String = ""
|
||||
|
||||
@Field.Computed("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
|
||||
}
|
||||
let title = partialObject.value(for: \.$title)
|
||||
let name = partialObject.value(for: \.$name)
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
```
|
||||
- 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 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>(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
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,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) where Coder.FieldStoredValue == V {
|
||||
|
||||
self.init(
|
||||
defaultValue: initial,
|
||||
keyPath: keyPath,
|
||||
isOptional: false,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
valueTransformer: { Internals.AnyFieldCoder(fieldCoderType) },
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
|
||||
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: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
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,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: @propertyWrapper
|
||||
|
||||
@available(*, unavailable)
|
||||
public var wrappedValue: V {
|
||||
|
||||
get { fatalError() }
|
||||
set { fatalError() }
|
||||
}
|
||||
|
||||
public var projectedValue: Self {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
public static subscript(
|
||||
_enclosingInstance instance: O,
|
||||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<O, V>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<O, Self>
|
||||
) -> V {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
instance.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!) as! V
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
instance.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return self.modify(field: instance[keyPath: storageKeyPath], for: instance.rawObject!, newValue: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: FieldProtocol
|
||||
|
||||
internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? {
|
||||
|
||||
Internals.assert(
|
||||
rawObject.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
let field = field as! Self
|
||||
if let customGetter = field.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(rawObject))
|
||||
}
|
||||
let keyPath = field.keyPath
|
||||
switch rawObject.value(forKey: keyPath) {
|
||||
|
||||
case let rawValue as V:
|
||||
return rawValue
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
internal static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) {
|
||||
|
||||
Internals.assert(
|
||||
rawObject.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
rawObject.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
let newValue = newValue as! V
|
||||
let field = field as! Self
|
||||
let keyPath = field.keyPath
|
||||
if let customSetter = field.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(rawObject), newValue)
|
||||
}
|
||||
return rawObject.setValue(newValue, forKey: keyPath)
|
||||
}
|
||||
|
||||
|
||||
// MARK: FieldAttributeProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> FieldAttributeProtocol.EntityDescriptionValues
|
||||
|
||||
internal var getter: CoreStoreManagedObject.CustomGetter? {
|
||||
|
||||
let keyPath = self.keyPath
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
switch rawObject.primitiveValue(forKey: keyPath) {
|
||||
|
||||
case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox:
|
||||
rawObject.setPrimitiveValue(valueBox.value, forKey: keyPath)
|
||||
return valueBox.value
|
||||
|
||||
case let value:
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(PartialObject<O>(rawObject))
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
internal var setter: CoreStoreManagedObject.CustomSetter? {
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
PartialObject<O>(rawObject),
|
||||
newValue as! V
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate init(
|
||||
defaultValue: @escaping () -> Any?,
|
||||
keyPath: KeyPathString,
|
||||
isOptional: Bool,
|
||||
versionHashModifier: @escaping () -> String?,
|
||||
renamingIdentifier: @escaping () -> String?,
|
||||
valueTransformer: @escaping () -> Internals.AnyFieldCoder?,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)?,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? ,
|
||||
affectedByKeyPaths: @escaping () -> Set<KeyPathString>) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
|
||||
let fieldCoder = valueTransformer()
|
||||
return (
|
||||
attributeType: .transformableAttributeType,
|
||||
isOptional: isOptional,
|
||||
isTransient: false,
|
||||
allowsExternalBinaryDataStorage: false,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
valueTransformer: fieldCoder,
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: Internals.AnyFieldCoder.TransformableDefaultValueCodingBox(
|
||||
defaultValue: defaultValue(),
|
||||
fieldCoder: fieldCoder
|
||||
)
|
||||
)
|
||||
}
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Coded where V: FieldOptionalType
|
||||
|
||||
extension FieldContainer.Coded where V: FieldOptionalType {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
@Field.Stored("title")
|
||||
var title: String = "Mr."
|
||||
|
||||
@Field.Stored("name")
|
||||
var name: String = ""
|
||||
|
||||
@Field.Computed("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
|
||||
}
|
||||
let title = partialObject.value(for: \.$title)
|
||||
let name = partialObject.value(for: \.$name)
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
```
|
||||
- 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 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>(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V = nil,
|
||||
_ keyPath: KeyPathString,
|
||||
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,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) 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,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
|
||||
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: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
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,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Coded where V: DefaultNSSecureCodable
|
||||
|
||||
extension FieldContainer.Coded where V: DefaultNSSecureCodable {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
@Field.Stored("title")
|
||||
var title: String = "Mr."
|
||||
|
||||
@Field.Stored("name")
|
||||
var name: String = ""
|
||||
|
||||
@Field.Computed("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
|
||||
}
|
||||
let title = partialObject.value(for: \.$title)
|
||||
let name = partialObject.value(for: \.$name)
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
```
|
||||
- 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 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: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
defaultValue: initial,
|
||||
keyPath: keyPath,
|
||||
isOptional: false,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding<V>.self) },
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSSecureCodable
|
||||
|
||||
extension FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSSecureCodable {
|
||||
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V = nil,
|
||||
_ 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,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
defaultValue: { initial().cs_wrappedValue },
|
||||
keyPath: keyPath,
|
||||
isOptional: true,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding<V.Wrapped>.self) },
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -79,14 +79,12 @@ extension FieldContainer {
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- 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 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 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,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
@@ -94,7 +92,6 @@ extension FieldContainer {
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
isOptional: false,
|
||||
renamingIdentifier: renamingIdentifier,
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
@@ -102,14 +99,13 @@ extension FieldContainer {
|
||||
}
|
||||
|
||||
/**
|
||||
Overload for compiler message only
|
||||
Overload for compiler error message only
|
||||
*/
|
||||
@available(*, unavailable, message: "Field.Computed properties are not allowed to have default values.")
|
||||
@available(*, unavailable, message: "Field.Computed properties are not allowed to have initial values, including `nil`.")
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
@@ -274,7 +270,6 @@ extension FieldContainer {
|
||||
fileprivate init(
|
||||
keyPath: KeyPathString,
|
||||
isOptional: Bool,
|
||||
renamingIdentifier: @escaping () -> String?,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)?,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? ,
|
||||
affectedByKeyPaths: @escaping () -> Set<KeyPathString>) {
|
||||
@@ -287,7 +282,8 @@ extension FieldContainer {
|
||||
isTransient: true,
|
||||
allowsExternalBinaryDataStorage: false,
|
||||
versionHashModifier: nil,
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
renamingIdentifier: nil,
|
||||
valueTransformer: nil,
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: nil
|
||||
)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
//
|
||||
// Field.PlistCoded.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Created by John Estropia on 2020/01/15.
|
||||
// Copyright © 2020 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -81,7 +81,7 @@ extension FieldContainer {
|
||||
- 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 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 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 affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
@@ -90,17 +90,18 @@ extension FieldContainer {
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
wrappedValue: initial,
|
||||
keyPath: keyPath,
|
||||
isOptional: false,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: renamingIdentifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
@@ -185,11 +186,14 @@ extension FieldContainer {
|
||||
return customGetter(PartialObject<O>(rawObject))
|
||||
}
|
||||
let keyPath = field.keyPath
|
||||
guard case let rawValue as V.FieldStoredNativeType = rawObject.value(forKey: keyPath) else {
|
||||
switch rawObject.value(forKey: keyPath) {
|
||||
|
||||
case let rawValue as V.FieldStoredNativeType:
|
||||
return V.cs_fromFieldStoredNativeType(rawValue)
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return V.cs_fromFieldStoredNativeType(rawValue)
|
||||
}
|
||||
|
||||
internal static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) {
|
||||
@@ -284,6 +288,7 @@ extension FieldContainer {
|
||||
allowsExternalBinaryDataStorage: false,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
valueTransformer: nil,
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: initial().cs_toFieldStoredNativeType()
|
||||
)
|
||||
@@ -334,7 +339,7 @@ extension FieldContainer.Stored where V: FieldOptionalType {
|
||||
- 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 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 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 affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
@@ -343,7 +348,7 @@ extension FieldContainer.Stored where V: FieldOptionalType {
|
||||
wrappedValue initial: @autoclosure @escaping () -> V = nil,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
@@ -353,7 +358,7 @@ extension FieldContainer.Stored where V: FieldOptionalType {
|
||||
keyPath: keyPath,
|
||||
isOptional: true,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: renamingIdentifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
|
||||
49
Sources/FieldAttributeProtocol.swift
Normal file
49
Sources/FieldAttributeProtocol.swift
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// FieldAttributeProtocol.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 Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - FieldAttributeProtocol
|
||||
|
||||
internal protocol FieldAttributeProtocol: FieldProtocol {
|
||||
|
||||
typealias EntityDescriptionValues = (
|
||||
attributeType: NSAttributeType,
|
||||
isOptional: Bool,
|
||||
isTransient: Bool,
|
||||
allowsExternalBinaryDataStorage: Bool,
|
||||
versionHashModifier: String?,
|
||||
renamingIdentifier: String?,
|
||||
valueTransformer: Internals.AnyFieldCoder?,
|
||||
affectedByKeyPaths: Set<KeyPathString>,
|
||||
defaultValue: Any?
|
||||
)
|
||||
|
||||
var entityDescriptionValues: () -> EntityDescriptionValues { get }
|
||||
var getter: CoreStoreManagedObject.CustomGetter? { get }
|
||||
var setter: CoreStoreManagedObject.CustomSetter? { get }
|
||||
}
|
||||
38
Sources/FieldCoderType.swift
Normal file
38
Sources/FieldCoderType.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// FieldCoderType.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 Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - FieldCoderType
|
||||
|
||||
public protocol FieldCoderType {
|
||||
|
||||
associatedtype FieldStoredValue
|
||||
|
||||
static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data?
|
||||
static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue?
|
||||
}
|
||||
87
Sources/FieldCoders.DefaultNSSecureCoding.swift
Normal file
87
Sources/FieldCoders.DefaultNSSecureCoding.swift
Normal file
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// FieldCoders.DefaultNSSecureCoding.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 Foundation
|
||||
|
||||
|
||||
// MARK: - FieldCoders
|
||||
|
||||
extension FieldCoders {
|
||||
|
||||
public struct DefaultNSSecureCoding<T: DefaultNSSecureCodable>: FieldCoderType {
|
||||
|
||||
// MARK: FieldCoderType
|
||||
|
||||
public typealias FieldStoredValue = T
|
||||
|
||||
public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? {
|
||||
|
||||
return ValueTransformer(forName: self.transformerName)?.reverseTransformedValue(fieldValue) as? Data
|
||||
}
|
||||
|
||||
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
|
||||
|
||||
return ValueTransformer(forName: self.transformerName)?.transformedValue(data) as! FieldStoredValue?
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static var transformerName: NSValueTransformerName {
|
||||
|
||||
if #available(iOS 12.0, tvOS 12.0, watchOS 5.0, macOS 10.14, *) {
|
||||
|
||||
return .secureUnarchiveFromDataTransformerName
|
||||
}
|
||||
else {
|
||||
|
||||
return .keyedUnarchiveFromDataTransformerName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public protocol DefaultNSSecureCodable: NSObject, NSSecureCoding {}
|
||||
|
||||
extension NSArray: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSDictionary: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSSet: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSString: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSNumber: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSDate: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSData: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSURL: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSUUID: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSNull: DefaultNSSecureCodable {}
|
||||
59
Sources/FieldCoders.Json.swift
Normal file
59
Sources/FieldCoders.Json.swift
Normal file
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// FieldCoders.Json.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 Foundation
|
||||
|
||||
|
||||
// MARK: - FieldCoders
|
||||
|
||||
extension FieldCoders {
|
||||
|
||||
// MARK: - Json
|
||||
|
||||
public struct Json<V: Codable>: FieldCoderType {
|
||||
|
||||
// MARK: FieldCoderType
|
||||
|
||||
public typealias FieldStoredValue = V
|
||||
|
||||
public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? {
|
||||
|
||||
guard let fieldValue = fieldValue else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return try? JSONEncoder().encode(fieldValue)
|
||||
}
|
||||
|
||||
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
|
||||
|
||||
guard let data = data else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return try? JSONDecoder().decode(FieldStoredValue.self, from: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Sources/FieldCoders.NSCoding.swift
Normal file
91
Sources/FieldCoders.NSCoding.swift
Normal file
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// FieldCoders.NSCoding.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 Foundation
|
||||
|
||||
|
||||
// MARK: - FieldCoders
|
||||
|
||||
extension FieldCoders {
|
||||
|
||||
// MARK: - NSCoding
|
||||
|
||||
public struct NSCoding<V: NSObject & Foundation.NSCoding>: FieldCoderType {
|
||||
|
||||
// MARK: FieldCoderType
|
||||
|
||||
public typealias FieldStoredValue = V
|
||||
|
||||
public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? {
|
||||
|
||||
guard let fieldValue = fieldValue else {
|
||||
|
||||
return nil
|
||||
}
|
||||
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
|
||||
|
||||
return try! NSKeyedArchiver.archivedData(
|
||||
withRootObject: fieldValue,
|
||||
requiringSecureCoding: self.requiresSecureCoding
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
return NSKeyedArchiver.archivedData(withRootObject: fieldValue)
|
||||
}
|
||||
}
|
||||
|
||||
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
|
||||
|
||||
guard let data = data else {
|
||||
|
||||
return nil
|
||||
}
|
||||
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
|
||||
|
||||
return try! NSKeyedUnarchiver.unarchivedObject(ofClass: FieldStoredValue.self, from: data)
|
||||
}
|
||||
else {
|
||||
|
||||
return NSKeyedUnarchiver.unarchiveObject(with: data) as! FieldStoredValue?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static var requiresSecureCoding: Bool {
|
||||
|
||||
switch FieldStoredValue.self {
|
||||
|
||||
case let valueType as NSSecureCoding.Type:
|
||||
return valueType.supportsSecureCoding
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Sources/FieldCoders.Plist.swift
Normal file
59
Sources/FieldCoders.Plist.swift
Normal file
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// FieldCoders.Plist.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 Foundation
|
||||
|
||||
|
||||
// MARK: - FieldCoders
|
||||
|
||||
extension FieldCoders {
|
||||
|
||||
// MARK: - Plist
|
||||
|
||||
public struct Plist<V: Codable>: FieldCoderType {
|
||||
|
||||
// MARK: FieldCoderType
|
||||
|
||||
public typealias FieldStoredValue = V
|
||||
|
||||
public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? {
|
||||
|
||||
guard let fieldValue = fieldValue else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return try? PropertyListEncoder().encode(fieldValue)
|
||||
}
|
||||
|
||||
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
|
||||
|
||||
guard let data = data else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return try? PropertyListDecoder().decode(FieldStoredValue.self, from: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Sources/FieldCoders.swift
Normal file
30
Sources/FieldCoders.swift
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// FieldCoders.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.
|
||||
//
|
||||
|
||||
|
||||
/**
|
||||
Namespace for Built-in Field Coders
|
||||
*/
|
||||
public enum FieldCoders {}
|
||||
@@ -34,24 +34,3 @@ internal protocol FieldProtocol: PropertyProtocol {
|
||||
static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any?
|
||||
static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldAttributeProtocol
|
||||
|
||||
internal protocol FieldAttributeProtocol: FieldProtocol {
|
||||
|
||||
typealias EntityDescriptionValues = (
|
||||
attributeType: NSAttributeType,
|
||||
isOptional: Bool,
|
||||
isTransient: Bool,
|
||||
allowsExternalBinaryDataStorage: Bool,
|
||||
versionHashModifier: String?,
|
||||
renamingIdentifier: String?,
|
||||
affectedByKeyPaths: Set<KeyPathString>,
|
||||
defaultValue: Any?
|
||||
)
|
||||
|
||||
var entityDescriptionValues: () -> EntityDescriptionValues { get }
|
||||
var getter: CoreStoreManagedObject.CustomGetter? { get }
|
||||
var setter: CoreStoreManagedObject.CustomSetter? { get }
|
||||
}
|
||||
|
||||
222
Sources/Internals.AnyFieldCoder.swift
Normal file
222
Sources/Internals.AnyFieldCoder.swift
Normal file
@@ -0,0 +1,222 @@
|
||||
//
|
||||
// Internals.AnyFieldCoder.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 Foundation
|
||||
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
extension Internals {
|
||||
|
||||
// MARK: - AnyFieldCoder
|
||||
|
||||
internal struct AnyFieldCoder {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let transformerName: NSValueTransformerName
|
||||
internal let transformer: Foundation.ValueTransformer
|
||||
internal let encodeToStoredData: (_ fieldValue: Any?) -> Data?
|
||||
internal let decodeFromStoredData: (_ data: Data?) -> Any?
|
||||
|
||||
internal init<Coder: FieldCoderType>(_ fieldCoder: Coder.Type) {
|
||||
|
||||
let transformer = CustomValueTransformer(fieldCoder: fieldCoder)
|
||||
self.transformerName = transformer.id
|
||||
self.transformer = transformer
|
||||
self.encodeToStoredData = {
|
||||
|
||||
switch $0 {
|
||||
|
||||
case let value as Coder.FieldStoredValue:
|
||||
return fieldCoder.encodeToStoredData(value)
|
||||
|
||||
case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox:
|
||||
return fieldCoder.encodeToStoredData(valueBox.value as! Coder.FieldStoredValue?)
|
||||
|
||||
default:
|
||||
return fieldCoder.encodeToStoredData(nil)
|
||||
}
|
||||
}
|
||||
self.decodeFromStoredData = { fieldCoder.decodeFromStoredData($0) }
|
||||
}
|
||||
|
||||
internal init<V>(tag: UUID, encode: @escaping (V) -> Data?, decode: @escaping (Data?) -> V) {
|
||||
|
||||
let transformer = CustomValueTransformer(tag: tag)
|
||||
self.transformerName = transformer.id
|
||||
self.transformer = transformer
|
||||
self.encodeToStoredData = { encode($0 as! V) }
|
||||
self.decodeFromStoredData = { decode($0) }
|
||||
}
|
||||
|
||||
internal func register() {
|
||||
|
||||
let transformerName = self.transformerName
|
||||
if #available(iOS 12.0, tvOS 12.0, watchOS 5.0, macOS 10.14, *) {
|
||||
|
||||
if transformerName == .secureUnarchiveFromDataTransformerName {
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
switch transformerName {
|
||||
|
||||
case .keyedUnarchiveFromDataTransformerName,
|
||||
.unarchiveFromDataTransformerName,
|
||||
.isNotNilTransformerName,
|
||||
.isNilTransformerName,
|
||||
.negateBooleanTransformerName:
|
||||
return
|
||||
|
||||
case let transformerName:
|
||||
Self.cachedCoders[transformerName] = self
|
||||
|
||||
Foundation.ValueTransformer.setValueTransformer(
|
||||
self.transformer,
|
||||
forName: transformerName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate static var cachedCoders: [NSValueTransformerName: AnyFieldCoder] = [:]
|
||||
|
||||
|
||||
// MARK: - TransformableDefaultValueCodingBox
|
||||
|
||||
@objc(_CoreStore_Internals_TransformableDefaultValueCodingBox)
|
||||
internal final class TransformableDefaultValueCodingBox: NSObject, NSSecureCoding {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@objc
|
||||
internal dynamic let transformerName: String
|
||||
|
||||
@objc
|
||||
internal dynamic let data: Data
|
||||
|
||||
@nonobjc
|
||||
internal let value: Any?
|
||||
|
||||
internal init?(defaultValue: Any?, fieldCoder: Internals.AnyFieldCoder?) {
|
||||
|
||||
guard
|
||||
let fieldCoder = fieldCoder,
|
||||
let defaultValue = defaultValue,
|
||||
!(defaultValue is NSNull),
|
||||
let data = fieldCoder.encodeToStoredData(defaultValue)
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
self.transformerName = fieldCoder.transformerName.rawValue
|
||||
self.value = defaultValue
|
||||
self.data = data
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSSecureCoding
|
||||
|
||||
@objc
|
||||
dynamic class var supportsSecureCoding: Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSCoding
|
||||
|
||||
@objc
|
||||
dynamic required init?(coder aDecoder: NSCoder) {
|
||||
|
||||
guard
|
||||
case let transformerName as String = aDecoder.decodeObject(forKey: #keyPath(TransformableDefaultValueCodingBox.transformerName)),
|
||||
let transformer = ValueTransformer(forName: .init(transformerName)),
|
||||
case let data as Data = aDecoder.decodeObject(forKey: #keyPath(TransformableDefaultValueCodingBox.data)),
|
||||
let value = transformer.reverseTransformedValue(data)
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
self.transformerName = transformerName
|
||||
self.data = data
|
||||
self.value = value
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func encode(with coder: NSCoder) {
|
||||
|
||||
coder.encode(self.data, forKey: #keyPath(TransformableDefaultValueCodingBox.data))
|
||||
coder.encode(self.transformerName, forKey: #keyPath(TransformableDefaultValueCodingBox.transformerName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CustomValueTransformer
|
||||
|
||||
fileprivate final class CustomValueTransformer: ValueTransformer {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate let id: NSValueTransformerName
|
||||
|
||||
fileprivate init<Coder: FieldCoderType>(fieldCoder: Coder.Type) {
|
||||
|
||||
self.id = .init(rawValue: "CoreStore.FieldCoders.CustomValueTransformer<\(String(reflecting: fieldCoder))>.transformerName")
|
||||
}
|
||||
|
||||
fileprivate init(tag: UUID) {
|
||||
|
||||
self.id = .init(rawValue: "CoreStore.FieldCoders.CustomValueTransformer<\(tag.uuidString)>.transformerName")
|
||||
}
|
||||
|
||||
|
||||
// MARK: ValueTransformer
|
||||
|
||||
override class func transformedValueClass() -> AnyClass {
|
||||
|
||||
return NSData.self
|
||||
}
|
||||
|
||||
override class func allowsReverseTransformation() -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override func transformedValue(_ value: Any?) -> Any? {
|
||||
|
||||
return AnyFieldCoder.cachedCoders[self.id]?.encodeToStoredData(value) as Data?
|
||||
}
|
||||
|
||||
override func reverseTransformedValue(_ value: Any?) -> Any? {
|
||||
|
||||
return AnyFieldCoder.cachedCoders[self.id]?.decodeFromStoredData(value as! Data?)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,29 +33,49 @@ extension NSEntityDescription {
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveAttributeNames() -> [String: (attribute: NSAttributeDescription, versionHash: Data)] {
|
||||
return self.attributesByName.reduce(into: [:], { (result, attribute: (name: String, description: NSAttributeDescription)) in
|
||||
result[attribute.name] = (attribute.description, attribute.description.versionHash)
|
||||
})
|
||||
|
||||
return self.attributesByName.reduce(
|
||||
into: [:],
|
||||
{ (result, attribute: (name: String, description: NSAttributeDescription)) in
|
||||
|
||||
result[attribute.name] = (attribute.description, attribute.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveAttributeRenamingIdentities() -> [String: (attribute: NSAttributeDescription, versionHash: Data)] {
|
||||
return self.attributesByName.reduce(into: [:], { (result, attribute: (name: String, description: NSAttributeDescription)) in
|
||||
result[attribute.description.renamingIdentifier ?? attribute.name] = (attribute.description, attribute.description.versionHash)
|
||||
})
|
||||
|
||||
return self.attributesByName.reduce(
|
||||
into: [:],
|
||||
{ (result, attribute: (name: String, description: NSAttributeDescription)) in
|
||||
|
||||
result[attribute.description.renamingIdentifier ?? attribute.name] = (attribute.description, attribute.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveRelationshipNames() -> [String: (relationship: NSRelationshipDescription, versionHash: Data)] {
|
||||
return self.relationshipsByName.reduce(into: [:], { (result, relationship: (name: String, description: NSRelationshipDescription)) in
|
||||
result[relationship.name] = (relationship.description, relationship.description.versionHash)
|
||||
})
|
||||
|
||||
return self.relationshipsByName.reduce(
|
||||
into: [:],
|
||||
{ (result, relationship: (name: String, description: NSRelationshipDescription)) in
|
||||
|
||||
result[relationship.name] = (relationship.description, relationship.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveRelationshipRenamingIdentities() -> [String: (relationship: NSRelationshipDescription, versionHash: Data)] {
|
||||
return self.relationshipsByName.reduce(into: [:], { (result, relationship: (name: String, description: NSRelationshipDescription)) in
|
||||
result[relationship.description.renamingIdentifier ?? relationship.name] = (relationship.description, relationship.description.versionHash)
|
||||
})
|
||||
|
||||
return self.relationshipsByName.reduce(
|
||||
into: [:],
|
||||
{ (result, relationship: (name: String, description: NSRelationshipDescription)) in
|
||||
|
||||
result[relationship.description.renamingIdentifier ?? relationship.name] = (relationship.description, relationship.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,15 +33,25 @@ extension NSManagedObjectModel {
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveNames() -> [String: (entity: NSEntityDescription, versionHash: Data)] {
|
||||
return self.entitiesByName.reduce(into: [:], { (result, entity: (name: String, description: NSEntityDescription)) in
|
||||
result[entity.name] = (entity.description, entity.description.versionHash)
|
||||
})
|
||||
|
||||
return self.entitiesByName.reduce(
|
||||
into: [:],
|
||||
{ (result, entity: (name: String, description: NSEntityDescription)) in
|
||||
|
||||
result[entity.name] = (entity.description, entity.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveRenamingIdentities() -> [String: (entity: NSEntityDescription, versionHash: Data)] {
|
||||
return self.entitiesByName.reduce(into: [:], { (result, entity: (name: String, description: NSEntityDescription)) in
|
||||
result[entity.description.renamingIdentifier ?? entity.name] = (entity.description, entity.description.versionHash)
|
||||
})
|
||||
|
||||
return self.entitiesByName.reduce(
|
||||
into: [:],
|
||||
{ (result, entity: (name: String, description: NSEntityDescription)) in
|
||||
|
||||
result[entity.description.renamingIdentifier ?? entity.name] = (entity.description, entity.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,10 @@ public struct PartialObject<O: CoreStoreObject> {
|
||||
case let value as V:
|
||||
return value
|
||||
|
||||
default:
|
||||
return nil as Any? as! V // filter NSNull
|
||||
case nil,
|
||||
is NSNull,
|
||||
_? /* any other unrelated type */ :
|
||||
return nil as Any? as! V // There should be no uninitialized state at this point
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,15 +89,41 @@ public struct PartialObject<O: CoreStoreObject> {
|
||||
|
||||
This method does not invoke the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). This method is used primarily by subclasses that implement custom accessor methods that need direct access to the receiver’s private storage.
|
||||
*/
|
||||
public func primitiveValue<V>(for property: (O) -> FieldContainer<O>.Computed<V>) -> V {
|
||||
public func primitiveValue<V>(for property: (O) -> FieldContainer<O>.Computed<V>) -> V? {
|
||||
|
||||
switch self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) {
|
||||
|
||||
case let value as V:
|
||||
return value
|
||||
|
||||
default:
|
||||
return nil as Any? as! V // filter NSNull
|
||||
case nil,
|
||||
is NSNull,
|
||||
_? /* any other unrelated type */ :
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the specified property from the managed object’s private internal storage.
|
||||
|
||||
This method does not invoke the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). This method is used primarily by subclasses that implement custom accessor methods that need direct access to the receiver’s private storage.
|
||||
*/
|
||||
public func primitiveValue<V>(for property: (O) -> FieldContainer<O>.Coded<V>) -> V? {
|
||||
|
||||
let keyPath = property(O.meta).keyPath
|
||||
switch self.rawObject.primitiveValue(forKey: keyPath) {
|
||||
|
||||
case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox:
|
||||
rawObject.setPrimitiveValue(valueBox.value, forKey: keyPath)
|
||||
return valueBox.value as? V
|
||||
|
||||
case let value as V:
|
||||
return value
|
||||
|
||||
case nil,
|
||||
is NSNull,
|
||||
_? /* any other unrelated type */ :
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user