diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index eb34eaa..02fea11 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -33,6 +33,7 @@ class Animal: CoreStoreObject { let species = Value.Required("species", default: "Swift") let master = Relationship.ToOne("master") + let color = Transformable.Optional("color") } class Dog: Animal { @@ -69,12 +70,12 @@ class DynamicModelTests: BaseTestDataTestCase { Entity("Animal"), Entity("Dog"), Entity("Person") - ]/*, + ], versionLock: [ - "Animal": [0x2698c812ebbc3b97, 0x751e3fa3f04cf9, 0x51fd460d3babc82, 0x92b4ba735b5a3053], - "Dog": [0x5285f8e3aff69199, 0x62c3291b59f2ec7c, 0xbe5a571397a4117b, 0x97fb40f5b79ffbdc], - "Person": [0xae4060a59f990ef0, 0x8ac83a6e1411c130, 0xa29fea58e2e38ab6, 0x2071bb7e33d77887] - ]*/ + "Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a], + "Dog": [0xe3f0afeb109b283a, 0x29998d292938eb61, 0x6aab788333cfc2a3, 0x492ff1d295910ea7], + "Person": [0x66d8bbfd8b21561f, 0xcecec69ecae3570f, 0xc4b73d71256214ef, 0x89b99bfe3e013e8b] + ] ) ) self.prepareStack(dataStack, configurations: [nil]) { (stack) in @@ -100,6 +101,9 @@ class DynamicModelTests: BaseTestDataTestCase { animal.species .= "Sparrow" XCTAssertEqual(animal.species.value, "Sparrow") + animal.color .= .yellow + XCTAssertEqual(animal.color.value, UIColor.yellow) + let dog = transaction.create(Into()) XCTAssertEqual(dog.species.value, "Swift") XCTAssertEqual(dog.nickname.value, nil) diff --git a/Sources/Relationship.swift b/Sources/Relationship.swift index 066cd72..cfb679f 100644 --- a/Sources/Relationship.swift +++ b/Sources/Relationship.swift @@ -27,11 +27,6 @@ import CoreData import Foundation -// MARK: Operators - -infix operator .= : AssignmentPrecedence - - // MARK: - DynamicObject public extension DynamicObject where Self: CoreStoreObject { @@ -48,18 +43,6 @@ public enum RelationshipContainer { public final class ToOne: RelationshipProtocol { - // MARK: - - - public static func .= (_ relationship: RelationshipContainer.ToOne, _ value: D?) { - - relationship.value = value - } - - public static func .= (_ relationship: RelationshipContainer.ToOne, _ relationship2: RelationshipContainer.ToOne) { - - relationship.value = relationship2.value - } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) @@ -150,21 +133,6 @@ public enum RelationshipContainer { public final class ToManyOrdered: RelationshipProtocol { - public static func .= (_ relationship: RelationshipContainer.ToManyOrdered, _ value: [D]) { - - relationship.value = value - } - - public static func .= (_ relationship: RelationshipContainer.ToManyOrdered, _ value: C) where C.Iterator.Element == D { - - relationship.value = Array(value) - } - - public static func .= (_ relationship: RelationshipContainer.ToManyOrdered, _ relationship2: RelationshipContainer.ToManyOrdered) { - - relationship.value = relationship2.value - } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) @@ -271,28 +239,6 @@ public enum RelationshipContainer { public final class ToManyUnordered: RelationshipProtocol { - // MARK: - - - public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ value: Set) { - - relationship.value = value - } - - public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ value: C) where C.Iterator.Element == D { - - relationship.value = Set(value) - } - - public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyUnordered) { - - relationship.value = relationship2.value - } - - public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyOrdered) { - - relationship.value = Set(relationship2.value) - } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) @@ -416,7 +362,7 @@ public enum RelationshipContainer { } -// MARK: RelationshipContainer.ToManyOrdered: RandomAccessCollection +// MARK: - Convenience extension RelationshipContainer.ToManyOrdered: RandomAccessCollection { @@ -456,9 +402,6 @@ extension RelationshipContainer.ToManyOrdered: RandomAccessCollection { } } - -// MARK: RelationshipContainer.ToManyUnordered: Sequence - extension RelationshipContainer.ToManyUnordered: Sequence { public var count: Int { @@ -484,6 +427,117 @@ extension RelationshipContainer.ToManyUnordered: Sequence { } +// MARK: - Operations + +infix operator .= : AssignmentPrecedence +infix operator .== : ComparisonPrecedence + +extension RelationshipContainer.ToOne { + + public static func .= (_ relationship: RelationshipContainer.ToOne, _ newValue: D?) { + + relationship.value = newValue + } + + public static func .= (_ relationship: RelationshipContainer.ToOne, _ relationship2: RelationshipContainer.ToOne) { + + relationship.value = relationship2.value + } + + public static func .== (_ relationship: RelationshipContainer.ToOne, _ value: D?) -> Bool { + + return relationship.value == value + } + + public static func .== (_ value: D?, _ relationship: RelationshipContainer.ToOne) -> Bool { + + return value == relationship.value + } + + public static func .== (_ relationship: RelationshipContainer.ToOne, _ relationship2: RelationshipContainer.ToOne) -> Bool { + + return relationship.value == relationship2.value + } +} + +extension RelationshipContainer.ToManyOrdered { + + public static func .= (_ relationship: RelationshipContainer.ToManyOrdered, _ newValue: S) where S.Iterator.Element == D { + + relationship.nativeValue = NSOrderedSet(array: newValue.map({ $0.rawObject! })) + } + + public static func .= (_ relationship: RelationshipContainer.ToManyOrdered, _ relationship2: RelationshipContainer.ToManyOrdered) { + + relationship.nativeValue = relationship2.nativeValue + } + + public static func .== (_ relationship: RelationshipContainer.ToManyOrdered, _ collection: C) -> Bool where C.Iterator.Element == D { + + return relationship.nativeValue.elementsEqual( + collection.lazy.map({ $0.rawObject! }), + by: { ($0 as! NSManagedObject) == ($1 as! NSManagedObject) } + ) + } + + public static func .== (_ collection: C, _ relationship: RelationshipContainer.ToManyOrdered) -> Bool where C.Iterator.Element == D { + + return relationship.nativeValue.elementsEqual( + collection.lazy.map({ $0.rawObject! }), + by: { ($0 as! NSManagedObject) == ($1 as! NSManagedObject) } + ) + } + + public static func .== (_ relationship: RelationshipContainer.ToManyOrdered, _ relationship2: RelationshipContainer.ToManyOrdered) -> Bool { + + return relationship.nativeValue == relationship2.nativeValue + } +} + +extension RelationshipContainer.ToManyUnordered { + + public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ newValue: S) where S.Iterator.Element == D { + + relationship.nativeValue = NSSet(array: newValue.map({ $0.rawObject! })) + } + + public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyUnordered) { + + relationship.nativeValue = relationship2.nativeValue + } + + public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyOrdered) { + + relationship.nativeValue = NSSet(set: relationship2.nativeValue.set) + } + + public static func .== (_ relationship: RelationshipContainer.ToManyUnordered, _ sequence: S) -> Bool where S.Iterator.Element == D { + + return relationship.nativeValue.isEqual(to: Set(sequence.map({ $0.rawObject! }))) + } + + public static func .== (_ sequence: S, _ relationship: RelationshipContainer.ToManyUnordered) -> Bool where S.Iterator.Element == D { + + return relationship.nativeValue.isEqual(to: Set(sequence.map({ $0.rawObject! }))) + } + + public static func .== (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyUnordered) -> Bool { + + return relationship.nativeValue == relationship2.nativeValue + } + + public static func .== (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyOrdered) -> Bool { + + return relationship.nativeValue == NSSet(set: relationship2.nativeValue.set) + } + + public static func .== (_ relationship: RelationshipContainer.ToManyOrdered, _ relationship2: RelationshipContainer.ToManyUnordered) -> Bool { + + return NSSet(set: relationship.nativeValue.set) == relationship2.nativeValue + } +} + + // MARK: - RelationshipProtocol internal protocol RelationshipProtocol: class { diff --git a/Sources/SchemaHistory.swift b/Sources/SchemaHistory.swift index 944f036..cddf809 100644 --- a/Sources/SchemaHistory.swift +++ b/Sources/SchemaHistory.swift @@ -39,6 +39,14 @@ public final class SchemaHistory: ExpressibleByArrayLiteral { */ public let currentModelVersion: ModelVersion + /** + The schema for the current model version. The `DataStack` will try to migrate all `StorageInterface`s added to itself to this version, following the version steps provided by the `migrationChain`. + */ + public var currentSchema: DynamicSchema { + + return self.schema(for: self.currentModelVersion)! + } + /** The version string for the current model version. The `DataStack` will try to migrate all `StorageInterface`s added to itself to this version, following the version steps provided by the `migrationChain`. */ diff --git a/Sources/Value.swift b/Sources/Value.swift index dd1a44d..4785d7a 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -27,32 +27,29 @@ import CoreData import Foundation -// MARK: Operators - -infix operator .= : AssignmentPrecedence - - // MARK: - DynamicObject public extension DynamicObject where Self: CoreStoreObject { /** - The containing type for value attributes. `Value` attributes support any type that conform to `ImportableAttributeType`. + The containing type for value attributes. `Value` attributes support any type that conforms to `ImportableAttributeType`. ``` class Animal: CoreStoreObject { let species = Value.Required("species") let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") } ``` */ public typealias Value = ValueContainer /** - The containing type for transformable attributes. `Transformable` attributes support types that conform to `NSCoding` and `NSCopying`. + The containing type for transformable attributes. `Transformable` attributes support types that conforms to `NSCoding & NSCopying`. ``` class Animal: CoreStoreObject { - let ancestors = Transformable.Required("ancestors") - let descendants = Transformable.Optional("descendants") + let species = Value.Required("species") + let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") } ``` */ @@ -68,54 +65,54 @@ public extension DynamicObject where Self: CoreStoreObject { class Animal: CoreStoreObject { let species = Value.Required("species") let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") } ``` */ public enum ValueContainer { // MARK: - Required + /** - The containing type for required value attributes. Any type that conform to `ImportableAttributeType` are supported. + The containing type for required value attributes. Any type that conforms to `ImportableAttributeType` are supported. ``` class Animal: CoreStoreObject { let species = Value.Required("species") let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") } ``` */ public final class Required: AttributeProtocol { /** - Assigns a value to the attribute. The operation + Initializes the metadata for the attribute. ``` - animal.species .= "Swift" - ``` - is equivalent to - ``` - animal.species.value = "Swift" + class Person: CoreStoreObject { + let title = Value.Required("title", default: "Mr.") + let name = Value.Required( + "name", + customGetter: { (`self`, getValue) in + return "\(self.title.value) \(getValue())" + } + ) + } ``` + - parameter keyPath: the permanent attribute name for this attribute. + - parameter default: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified. + - parameter isIndexed: `true` if the property should be indexed for searching, otherwise `false`. Defaults to `false` if not specified. + - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. + - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the attributes of a property are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. + - parameter self: the `CoreStoreObject` + - parameter getValue: the original getter for the property + - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. + - parameter setValue: the original setter for the property + - parameter finalNewValue: the transformed new value + - parameter originalNewValue: the original new value */ - public static func .= (_ attribute: ValueContainer.Required, _ value: V) { - - attribute.value = value - } - - /** - Assigns a value to the attribute. The operation - ``` - animal.species .= anotherAnimal.species - ``` - is equivalent to - ``` - animal.species.value = anotherAnimal.species.value - ``` - */ - public static func .= (_ attribute: ValueContainer.Required, _ attribute2: ValueContainer.Required) { - - attribute.value = attribute2.value - } - - public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) { + public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void = { $1($2) }) { self.keyPath = keyPath self.isIndexed = isIndexed @@ -127,6 +124,9 @@ public enum ValueContainer { self.customSetter = customSetter } + /** + The property value. + */ public var value: V { get { @@ -206,64 +206,48 @@ public enum ValueContainer { // MARK: - Optional /** - The containing type for optional value attributes. Any type that conform to `ImportableAttributeType` are supported. + The containing type for optional value attributes. Any type that conforms to `ImportableAttributeType` are supported. ``` class Animal: CoreStoreObject { let species = Value.Required("species") let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") } ``` */ public final class Optional: AttributeProtocol { /** - Assigns a value to the attribute. The operation + Initializes the metadata for the attribute. ``` - animal.nickname .= "Taylor" - ``` - is equivalent to - ``` - animal.nickname.value = "Taylor" + class Person: CoreStoreObject { + let title = Value.Required("title", default: "Mr.") + let name = Value.Required( + "name", + customGetter: { (`self`, getValue) in + return "\(self.title.value) \(getValue())" + } + ) + } ``` + - parameter keyPath: the permanent attribute name for this attribute. + - parameter default: the initial value for the property when the object is first created. Defaults to `nil` if not specified. + - parameter isIndexed: `true` if the property should be indexed for searching, otherwise `false`. Defaults to `false` if not specified. + - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. + - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the attributes of a property are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. + - parameter self: the `CoreStoreObject` + - parameter getValue: the original getter for the property + - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. + - parameter setValue: the original setter for the property + - parameter finalNewValue: the transformed new value + - parameter originalNewValue: the original new value */ - public static func .= (_ attribute: ValueContainer.Optional, _ value: V?) { - - attribute.value = value - } - - /** - Assigns a value to the attribute. The operation - ``` - animal.nickname .= anotherAnimal.nickname - ``` - is equivalent to - ``` - animal.nickname.value = anotherAnimal.nickname.value - ``` - */ - public static func .= (_ attribute: ValueContainer.Optional, _ attribute2: ValueContainer.Optional) { - - attribute.value = attribute2.value - } - - /** - Assigns a value to the attribute. The operation - ``` - animal.nickname .= anotherAnimal.nickname - ``` - is equivalent to - ``` - animal.nickname.value = anotherAnimal.nickname.value - ``` - */ - public static func .= (_ attribute: ValueContainer.Optional, _ attribute2: ValueContainer.Required) { - - attribute.value = attribute2.value - } - - public init(_ keyPath: KeyPath, `default`: V? = nil, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) { + public init(_ keyPath: KeyPath, `default`: V? = nil, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void = { $1($2) }) { self.keyPath = keyPath + self.isIndexed = isIndexed self.isTransient = isTransient self.defaultValue = `default`?.cs_toImportableNativeType() self.versionHashModifier = versionHashModifier @@ -272,6 +256,9 @@ public enum ValueContainer { self.customSetter = customSetter } + /** + The property value. + */ public var value: V? { get { @@ -328,7 +315,7 @@ public enum ValueContainer { public let keyPath: KeyPath internal let isOptional = true - internal let isIndexed = false + internal let isIndexed: Bool internal let isTransient: Bool internal let defaultValue: Any? internal let versionHashModifier: String? @@ -350,23 +337,54 @@ public enum ValueContainer { // MARK: - TransformableContainer +/** + The containing type for transformable attributes. Use the `DynamicObject.Transformable` typealias instead for shorter syntax. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species") + let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") + } + ``` + */ public enum TransformableContainer { // MARK: - Required + /** + The containing type for transformable attributes. Any type that conforms to `NSCoding & NSCopying` are supported. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species") + let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") + } + ``` + */ public final class Required: AttributeProtocol { - public static func .= (_ attribute: TransformableContainer.Required, _ value: V) { - - attribute.value = value - } - - public static func .= (_ attribute: TransformableContainer.Required, _ attribute2: TransformableContainer.Required) { - - attribute.value = attribute2.value - } - - public init(_ keyPath: KeyPath, `default`: V, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) { + /** + Initializes the metadata for the attribute. + ``` + class Animal: CoreStoreObject { + let color = Transformable.Optional("color") + } + ``` + - parameter keyPath: the permanent attribute name for this attribute. + - parameter default: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified. + - parameter isIndexed: `true` if the property should be indexed for searching, otherwise `false`. Defaults to `false` if not specified. + - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. + - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the attributes of a property are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. + - parameter self: the `CoreStoreObject` + - parameter getValue: the original getter for the property + - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. + - parameter setValue: the original setter for the property + - parameter finalNewValue: the transformed new value + - parameter originalNewValue: the original new value + */ + public init(_ keyPath: KeyPath, `default`: V, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void = { $1($2) }) { self.keyPath = keyPath self.defaultValue = `default` @@ -378,6 +396,9 @@ public enum TransformableContainer { self.customSetter = customSetter } + /** + The property value. + */ public var value: V { get { @@ -455,24 +476,40 @@ public enum TransformableContainer { // MARK: - Optional + /** + The containing type for optional transformable attributes. Any type that conforms to `NSCoding & NSCopying` are supported. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species") + let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") + } + ``` + */ public final class Optional: AttributeProtocol { - public static func .= (_ attribute: TransformableContainer.Optional, _ value: V) { - - attribute.value = value - } - - public static func .= (_ attribute: TransformableContainer.Optional, _ attribute2: TransformableContainer.Optional) { - - attribute.value = attribute2.value - } - - public static func .= (_ attribute: TransformableContainer.Optional, _ attribute2: TransformableContainer.Required) { - - attribute.value = attribute2.value - } - - public init(_ keyPath: KeyPath, `default`: V? = nil, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) { + /** + Initializes the metadata for the attribute. + ``` + class Animal: CoreStoreObject { + let color = Transformable.Optional("color") + } + ``` + - parameter keyPath: the permanent attribute name for this attribute. + - parameter default: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified. + - parameter isIndexed: `true` if the property should be indexed for searching, otherwise `false`. Defaults to `false` if not specified. + - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. + - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the attributes of a property are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. + - parameter self: the `CoreStoreObject` + - parameter getValue: the original getter for the property + - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. + - parameter setValue: the original setter for the property + - parameter finalNewValue: the transformed new value + - parameter originalNewValue: the original new value + */ + public init(_ keyPath: KeyPath, `default`: V? = nil, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void = { $1($2) }) { self.keyPath = keyPath self.defaultValue = `default` @@ -484,6 +521,9 @@ public enum TransformableContainer { self.customSetter = customSetter } + /** + The property value. + */ public var value: V? { get { @@ -539,7 +579,7 @@ public enum TransformableContainer { public let keyPath: KeyPath - internal let isOptional = false + internal let isOptional = true internal let isIndexed: Bool internal let isTransient: Bool internal let defaultValue: Any? @@ -560,6 +600,174 @@ public enum TransformableContainer { } +// MARK: - Operations + +infix operator .= : AssignmentPrecedence +infix operator .== : ComparisonPrecedence + +extension ValueContainer.Required { + + /** + Assigns a value to the attribute. The operation + ``` + animal.species .= "Swift" + ``` + is equivalent to + ``` + animal.species.value = "Swift" + ``` + */ + public static func .= (_ attribute: ValueContainer.Required, _ newValue: V) { + + attribute.value = newValue + } + + /** + Assigns a value from another attribute. The operation + ``` + animal.species .= anotherAnimal.species + ``` + is equivalent to + ``` + animal.species.value = anotherAnimal.species.value + ``` + */ + public static func .= (_ attribute: ValueContainer.Required, _ attribute2: ValueContainer.Required) { + + attribute.value = attribute2.value + } +} + +extension ValueContainer.Optional { + + /** + Assigns an optional value to the attribute. The operation + ``` + animal.nickname .= "Taylor" + ``` + is equivalent to + ``` + animal.nickname.value = "Taylor" + ``` + */ + public static func .= (_ attribute: ValueContainer.Optional, _ newValue: V?) { + + attribute.value = newValue + } + + /** + Assigns an optional value from another attribute. The operation + ``` + animal.nickname .= anotherAnimal.nickname + ``` + is equivalent to + ``` + animal.nickname.value = anotherAnimal.nickname.value + ``` + */ + public static func .= (_ attribute: ValueContainer.Optional, _ attribute2: ValueContainer.Optional) { + + attribute.value = attribute2.value + } + + /** + Assigns a value from another attribute. The operation + ``` + animal.nickname .= anotherAnimal.species + ``` + is equivalent to + ``` + animal.nickname.value = anotherAnimal.species.value + ``` + */ + public static func .= (_ attribute: ValueContainer.Optional, _ attribute2: ValueContainer.Required) { + + attribute.value = attribute2.value + } +} + +extension TransformableContainer.Required { + + /** + Assigns a transformable value to the attribute. The operation + ``` + animal.color .= UIColor.red + ``` + is equivalent to + ``` + animal.color.value = UIColor.red + ``` + */ + public static func .= (_ attribute: TransformableContainer.Required, _ newValue: V) { + + attribute.value = newValue + } + + /** + Assigns a transformable value from another attribute. The operation + ``` + animal.nickname .= anotherAnimal.species + ``` + is equivalent to + ``` + animal.nickname.value = anotherAnimal.species.value + ``` + */ + public static func .= (_ attribute: TransformableContainer.Required, _ attribute2: TransformableContainer.Required) { + + attribute.value = attribute2.value + } +} + +extension TransformableContainer.Optional { + + /** + Assigns an optional transformable value to the attribute. The operation + ``` + animal.color .= UIColor.red + ``` + is equivalent to + ``` + animal.color.value = UIColor.red + ``` + */ + public static func .= (_ attribute: TransformableContainer.Optional, _ newValue: V?) { + + attribute.value = newValue + } + + /** + Assigns an optional transformable value from another attribute. The operation + ``` + animal.color .= anotherAnimal.color + ``` + is equivalent to + ``` + animal.color.value = anotherAnimal.color.value + ``` + */ + public static func .= (_ attribute: TransformableContainer.Optional, _ attribute2: TransformableContainer.Optional) { + + attribute.value = attribute2.value + } + + /** + Assigns a transformable value from another attribute. The operation + ``` + animal.color .= anotherAnimal.color + ``` + is equivalent to + ``` + animal.color.value = anotherAnimal.color.value + ``` + */ + public static func .= (_ attribute: TransformableContainer.Optional, _ attribute2: TransformableContainer.Required) { + + attribute.value = attribute2.value + } +} + + // MARK: - AttributeProtocol internal protocol AttributeProtocol: class {