From da9e8c15507127b10f3702229b677e0a158882fa Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sun, 28 May 2017 10:50:25 +0900 Subject: [PATCH] disallow "empty" default values on some ImportableAttributeTypes --- Sources/DynamicSchema+Convenience.swift | 5 +- Sources/ImportableAttributeType.swift | 355 +++++++++++++----------- Sources/Value.swift | 41 ++- 3 files changed, 238 insertions(+), 163 deletions(-) diff --git a/Sources/DynamicSchema+Convenience.swift b/Sources/DynamicSchema+Convenience.swift index aeb98bb..45ad08b 100644 --- a/Sources/DynamicSchema+Convenience.swift +++ b/Sources/DynamicSchema+Convenience.swift @@ -32,7 +32,7 @@ import Foundation public extension DynamicSchema { /** - Prints the `DynamicSchema` as their corresponding `CoreStoreObject` Swift declarations. This is useful for converting current `XcodeDataModelSchema`-based models into the new `CoreStoreSchema` framework. Additional adjustments may need to be done to the generated source code for "Transformable" attributes. + Prints the `DynamicSchema` as their corresponding `CoreStoreObject` Swift declarations. This is useful for converting current `XcodeDataModelSchema`-based models into the new `CoreStoreSchema` framework. Additional adjustments may need to be done to the generated source code; for example: `Transformable` concrete types need to be provided, as well as `default` values. - Important: After transitioning to the new `CoreStoreSchema` framework, it is recommended to add the new schema as a new version that the existing versions' `XcodeDataModelSchema` can migrate to. It is discouraged to load existing SQLite files created with `XcodeDataModelSchema` directly into a `CoreStoreSchema`. - returns: a string that represents the source code for the `DynamicSchema` as their corresponding `CoreStoreObject` Swift declarations. @@ -153,8 +153,7 @@ public extension DynamicSchema { } case .dateAttributeType: valueType = Date.self - if let defaultValue = (attribute.defaultValue as! Date.ImportableNativeType?).flatMap(Date.cs_fromImportableNativeType), - defaultValue != Date.cs_emptyValue() { + if let defaultValue = (attribute.defaultValue as! Date.ImportableNativeType?).flatMap(Date.cs_fromImportableNativeType) { defaultString = ", default: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))" } diff --git a/Sources/ImportableAttributeType.swift b/Sources/ImportableAttributeType.swift index 893ef3c..3770a5d 100644 --- a/Sources/ImportableAttributeType.swift +++ b/Sources/ImportableAttributeType.swift @@ -64,12 +64,6 @@ public protocol ImportableAttributeType: QueryableAttributeType { */ associatedtype ImportableNativeType: QueryableNativeType - /** - Returns the default "empty" value for this type. - */ - @inline(__always) - static func cs_emptyValue() -> Self - /** Creates an instance of this type from its `ImportableNativeType` value. */ @@ -84,18 +78,31 @@ public protocol ImportableAttributeType: QueryableAttributeType { } +// MARK: - EmptyableAttributeType + +/** + `ImportableAttributeType`s that have a natural "empty" value. Example: `0` for `Int`, `""` for `String`. + + - Discussion: Not all `ImportableAttributeType`s can have empty values. `URL`s and `Date`s for example have no obvious empty values. + */ +public protocol EmptyableAttributeType: ImportableAttributeType { + + /** + Returns the default "empty" value for this type. + */ + @inline(__always) + static func cs_emptyValue() -> Self +} + + // MARK: - Bool -extension Bool: ImportableAttributeType { +extension Bool: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSNumber - @inline(__always) - public static func cs_emptyValue() -> Bool { - - return false - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Bool? { @@ -107,21 +114,26 @@ extension Bool: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> Bool { + + return false + } } // MARK: - CGFloat -extension CGFloat: ImportableAttributeType { +extension CGFloat: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSNumber - @inline(__always) - public static func cs_emptyValue() -> CGFloat { - - return 0 - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> CGFloat? { @@ -133,21 +145,26 @@ extension CGFloat: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> CGFloat { + + return 0 + } } // MARK: - Data -extension Data: ImportableAttributeType { +extension Data: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSData - @inline(__always) - public static func cs_emptyValue() -> Data { - - return Data() - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Data? { @@ -159,6 +176,15 @@ extension Data: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> Data { + + return Data() + } } @@ -166,13 +192,9 @@ extension Data: ImportableAttributeType { extension Date: ImportableAttributeType { - public typealias ImportableNativeType = NSDate + // MARK: ImportableAttributeType - @inline(__always) - public static func cs_emptyValue() -> Date { - - return Date(timeIntervalSinceReferenceDate: 0) - } + public typealias ImportableNativeType = NSDate @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Date? { @@ -190,16 +212,12 @@ extension Date: ImportableAttributeType { // MARK: - Double -extension Double: ImportableAttributeType { +extension Double: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSNumber - @inline(__always) - public static func cs_emptyValue() -> Double { - - return 0 - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Double? { @@ -211,21 +229,26 @@ extension Double: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> Double { + + return 0 + } } // MARK: - Float -extension Float: ImportableAttributeType { +extension Float: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSNumber - @inline(__always) - public static func cs_emptyValue() -> Float { - - return 0 - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Float? { @@ -237,21 +260,26 @@ extension Float: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> Float { + + return 0 + } } // MARK: - Int -extension Int: ImportableAttributeType { +extension Int: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSNumber - @inline(__always) - public static func cs_emptyValue() -> Int { - - return 0 - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Int? { @@ -263,21 +291,26 @@ extension Int: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> Int { + + return 0 + } } // MARK: - Int8 -extension Int8: ImportableAttributeType { +extension Int8: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSNumber - @inline(__always) - public static func cs_emptyValue() -> Int8 { - - return 0 - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Int8? { @@ -289,21 +322,26 @@ extension Int8: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> Int8 { + + return 0 + } } // MARK: - Int16 -extension Int16: ImportableAttributeType { +extension Int16: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSNumber - @inline(__always) - public static func cs_emptyValue() -> Int16 { - - return 0 - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Int16? { @@ -315,21 +353,26 @@ extension Int16: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> Int16 { + + return 0 + } } // MARK: - Int32 -extension Int32: ImportableAttributeType { +extension Int32: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSNumber - @inline(__always) - public static func cs_emptyValue() -> Int32 { - - return 0 - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Int32? { @@ -341,21 +384,26 @@ extension Int32: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> Int32 { + + return 0 + } } // MARK: - Int64 -extension Int64: ImportableAttributeType { +extension Int64: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSNumber - @inline(__always) - public static func cs_emptyValue() -> Int64 { - - return 0 - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Int64? { @@ -367,21 +415,26 @@ extension Int64: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> Int64 { + + return 0 + } } // MARK: - NSData -extension NSData: ImportableAttributeType { +extension NSData: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSData - @nonobjc @inline(__always) - public class func cs_emptyValue() -> Self { - - return self.init() - } - @nonobjc @inline(__always) public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? { @@ -393,6 +446,15 @@ extension NSData: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @nonobjc @inline(__always) + public class func cs_emptyValue() -> Self { + + return self.init() + } } @@ -400,13 +462,9 @@ extension NSData: ImportableAttributeType { extension NSDate: ImportableAttributeType { - public typealias ImportableNativeType = NSDate + // MARK: ImportableAttributeType - @nonobjc @inline(__always) - public class func cs_emptyValue() -> Self { - - return self.init(timeIntervalSinceReferenceDate: 0) - } + public typealias ImportableNativeType = NSDate @nonobjc @inline(__always) public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? { @@ -424,16 +482,12 @@ extension NSDate: ImportableAttributeType { // MARK: - NSNumber -extension NSNumber: ImportableAttributeType { +extension NSNumber: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSNumber - @nonobjc @inline(__always) - public class func cs_emptyValue() -> Self { - - return self.init() - } - @nonobjc @inline(__always) public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? { @@ -445,21 +499,26 @@ extension NSNumber: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @nonobjc @inline(__always) + public class func cs_emptyValue() -> Self { + + return self.init() + } } // MARK: - NSString -extension NSString: ImportableAttributeType { +extension NSString: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSString - @nonobjc @inline(__always) - public class func cs_emptyValue() -> Self { - - return self.init() - } - @nonobjc @inline(__always) public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? { @@ -471,6 +530,15 @@ extension NSString: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @nonobjc @inline(__always) + public class func cs_emptyValue() -> Self { + + return self.init() + } } @@ -478,13 +546,9 @@ extension NSString: ImportableAttributeType { extension NSURL: ImportableAttributeType { - public typealias ImportableNativeType = NSString + // MARK: ImportableAttributeType - @nonobjc @inline(__always) - public class func cs_emptyValue() -> Self { - - return self.init(string: "")! - } + public typealias ImportableNativeType = NSString @nonobjc @inline(__always) public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? { @@ -504,16 +568,9 @@ extension NSURL: ImportableAttributeType { extension NSUUID: ImportableAttributeType { - public typealias ImportableNativeType = NSString + // MARK: ImportableAttributeType - public class func cs_emptyValue() -> Self { - - enum Static { - - static var zero = Array(repeating: 0, count: 16) - } - return self.init(uuidBytes: &Static.zero) - } + public typealias ImportableNativeType = NSString @nonobjc @inline(__always) public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? { @@ -531,16 +588,12 @@ extension NSUUID: ImportableAttributeType { // MARK: - String -extension String: ImportableAttributeType { +extension String: ImportableAttributeType, EmptyableAttributeType { + + // MARK: ImportableAttributeType public typealias ImportableNativeType = NSString - @inline(__always) - public static func cs_emptyValue() -> String { - - return "" - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> String? { @@ -552,6 +605,15 @@ extension String: ImportableAttributeType { return self.cs_toQueryableNativeType() } + + + // MARK: EmptyableAttributeType + + @inline(__always) + public static func cs_emptyValue() -> String { + + return "" + } } @@ -559,16 +621,9 @@ extension String: ImportableAttributeType { extension URL: ImportableAttributeType { - public typealias ImportableNativeType = NSString + // MARK: ImportableAttributeType - public static func cs_emptyValue() -> URL { - - enum Static { - - static let empty = URL(string: "")! - } - return Static.empty - } + public typealias ImportableNativeType = NSString @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> URL? { @@ -588,20 +643,9 @@ extension URL: ImportableAttributeType { extension UUID: ImportableAttributeType { - public typealias ImportableNativeType = NSString + // MARK: ImportableAttributeType - public static func cs_emptyValue() -> UUID { - - enum Static { - - static let empty: UUID = cs_lazy { - - var zero = Array(repeating: 0, count: 16) - return NSUUID(uuidBytes: &zero) as UUID - } - } - return Static.empty - } + public typealias ImportableNativeType = NSString @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> UUID? { @@ -623,11 +667,6 @@ extension RawRepresentable where RawValue: ImportableAttributeType { public typealias ImportableNativeType = RawValue.ImportableNativeType - public static func cs_emptyValue() -> Self { - - return self.init(rawValue: RawValue.cs_emptyValue())! - } - @inline(__always) public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? { diff --git a/Sources/Value.swift b/Sources/Value.swift index babd6b4..5e7d7c4 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -102,7 +102,7 @@ public enum ValueContainer { } ``` - parameter keyPath: the permanent attribute name for this property. - - 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 default: the initial value for the property when the object is first created. For types that implement `EmptyableAttributeType`s, this argument may be omitted and the type's "empty" value will be used instead (e.g. `false` for `Bool`, `0` for `Int`, `""` for `String`, etc.) - 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 properties are unchanged but the format or content of its data are changed.) @@ -115,7 +115,7 @@ public enum ValueContainer { - parameter finalNewValue: the transformed new value - parameter originalNewValue: the original new 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: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void = { $1($2) }) { + 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.isIndexed = isIndexed @@ -338,6 +338,43 @@ public enum ValueContainer { } } +public extension ValueContainer.Required where V: EmptyableAttributeType { + + /** + Initializes the metadata for the property. This convenience initializer uses the `EmptyableAttributeType`'s "empty" value as the initial value for the property when the object is first created (e.g. `false` for `Bool`, `0` for `Int`, `""` for `String`, etc.) + ``` + class Person: CoreStoreObject { + let title = Value.Required("title") // initial value defaults to empty string + } + ``` + - parameter keyPath: the permanent attribute name for this property. + - 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 properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. + - parameter self: the `CoreStoreObject` + - parameter getValue: the original getter for the property + - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. + - parameter setValue: the original setter for the property + - parameter finalNewValue: the transformed new value + - parameter originalNewValue: the original new value + */ + public convenience init(_ keyPath: KeyPath, 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.init( + keyPath, + default: V.cs_emptyValue(), + isIndexed: isIndexed, + isTransient: isTransient, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + customGetter: customGetter, + customSetter: customSetter + ) + } +} + // MARK: - TransformableContainer