Value.Required now requires an "initial:" parameter. For previous CoreStoreObject users, use the appropriate empty value for your existing properties (0 for numeric types, false for Bool, "" for String)

This commit is contained in:
John Rommel Estropia
2017-08-05 00:03:17 +09:00
committed by John Estropia
parent a366bcf1a3
commit 28b43f33fa
11 changed files with 868 additions and 820 deletions

View File

@@ -0,0 +1,47 @@
//
// AttributeProtocol.swift
// CoreStore
//
// Copyright © 2017 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: - AttributeProtocol
internal protocol AttributeProtocol: class {
static var attributeType: NSAttributeType { get }
var keyPath: KeyPath { get }
var isOptional: Bool { get }
var isIndexed: Bool { get }
var isTransient: Bool { get }
var versionHashModifier: () -> String? { get }
var renamingIdentifier: () -> String? { get }
var defaultValue: () -> Any? { get }
var affectedByKeyPaths: () -> Set<String> { get }
weak var parentObject: CoreStoreObject? { get set }
var getter: CoreStoreManagedObject.CustomGetter? { get }
var setter: CoreStoreManagedObject.CustomSetter? { get }
}

View File

@@ -295,8 +295,8 @@ public final class CoreStoreSchema: DynamicSchema {
description.isIndexed = attribute.isIndexed
description.defaultValue = attribute.defaultValue()
description.isTransient = attribute.isTransient
description.versionHashModifier = attribute.versionHashModifier
description.renamingIdentifier = attribute.renamingIdentifier
description.versionHashModifier = attribute.versionHashModifier()
description.renamingIdentifier = attribute.renamingIdentifier()
propertyDescriptions.append(description)
keyPathsByAffectedKeyPaths[attribute.keyPath] = attribute.affectedByKeyPaths()
customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter)
@@ -308,8 +308,8 @@ public final class CoreStoreSchema: DynamicSchema {
description.maxCount = relationship.maxCount
description.isOrdered = relationship.isOrdered
description.deleteRule = relationship.deleteRule
description.versionHashModifier = relationship.versionHashModifier
description.renamingIdentifier = relationship.renamingIdentifier
description.versionHashModifier = relationship.versionHashModifier()
description.renamingIdentifier = relationship.renamingIdentifier()
propertyDescriptions.append(description)
keyPathsByAffectedKeyPaths[relationship.keyPath] = relationship.affectedByKeyPaths()

View File

@@ -96,71 +96,62 @@ public extension DynamicSchema {
case .integer16AttributeType:
valueType = Int16.self
if let defaultValue = (attribute.defaultValue as! Int16.ImportableNativeType?).flatMap(Int16.cs_fromImportableNativeType),
defaultValue != Int16.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Int16.ImportableNativeType?).flatMap(Int16.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue)"
defaultString = ", initial: \(defaultValue)"
}
case .integer32AttributeType:
valueType = Int32.self
if let defaultValue = (attribute.defaultValue as! Int32.ImportableNativeType?).flatMap(Int32.cs_fromImportableNativeType),
defaultValue != Int32.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Int32.ImportableNativeType?).flatMap(Int32.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue)"
defaultString = ", initial: \(defaultValue)"
}
case .integer64AttributeType:
valueType = Int64.self
if let defaultValue = (attribute.defaultValue as! Int64.ImportableNativeType?).flatMap(Int64.cs_fromImportableNativeType),
defaultValue != Int64.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Int64.ImportableNativeType?).flatMap(Int64.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue)"
defaultString = ", initial: \(defaultValue)"
}
case .decimalAttributeType:
valueType = NSDecimalNumber.self
if let defaultValue = (attribute.defaultValue as! NSDecimalNumber.ImportableNativeType?).flatMap(NSDecimalNumber.cs_fromImportableNativeType),
defaultValue != NSDecimalNumber.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! NSDecimalNumber.ImportableNativeType?).flatMap(NSDecimalNumber.cs_fromImportableNativeType) {
defaultString = ", default: NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")"
defaultString = ", initial: NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")"
}
case .doubleAttributeType:
valueType = Double.self
if let defaultValue = (attribute.defaultValue as! Double.ImportableNativeType?).flatMap(Double.cs_fromImportableNativeType),
defaultValue != Double.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Double.ImportableNativeType?).flatMap(Double.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue)"
defaultString = ", initial: \(defaultValue)"
}
case .floatAttributeType:
valueType = Float.self
if let defaultValue = (attribute.defaultValue as! Float.ImportableNativeType?).flatMap(Float.cs_fromImportableNativeType),
defaultValue != Float.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Float.ImportableNativeType?).flatMap(Float.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue)"
defaultString = ", initial: \(defaultValue)"
}
case .stringAttributeType:
valueType = String.self
if let defaultValue = (attribute.defaultValue as! String.ImportableNativeType?).flatMap(String.cs_fromImportableNativeType),
defaultValue != String.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! String.ImportableNativeType?).flatMap(String.cs_fromImportableNativeType) {
// TODO: escape strings
defaultString = ", default: \"\(defaultValue)\""
defaultString = ", initial: \"\(defaultValue)\""
}
case .booleanAttributeType:
valueType = Bool.self
if let defaultValue = (attribute.defaultValue as! Bool.ImportableNativeType?).flatMap(Bool.cs_fromImportableNativeType),
defaultValue != Bool.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Bool.ImportableNativeType?).flatMap(Bool.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue ? "true" : "false")"
defaultString = ", initial: \(defaultValue ? "true" : "false")"
}
case .dateAttributeType:
valueType = Date.self
if let defaultValue = (attribute.defaultValue as! Date.ImportableNativeType?).flatMap(Date.cs_fromImportableNativeType) {
defaultString = ", default: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))"
defaultString = ", initial: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))"
}
case .binaryDataAttributeType:
valueType = Data.self
if let defaultValue = (attribute.defaultValue as! Data.ImportableNativeType?).flatMap(Data.cs_fromImportableNativeType),
defaultValue != Data.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Data.ImportableNativeType?).flatMap(Data.cs_fromImportableNativeType) {
let count = defaultValue.count
let bytes = defaultValue.withUnsafeBytes { (pointer: UnsafePointer<UInt8>) in
@@ -168,7 +159,7 @@ public extension DynamicSchema {
return (0 ..< (count / MemoryLayout<UInt8>.size))
.map({ "\("0x\(String(pointer[$0], radix: 16, uppercase: false))")" })
}
defaultString = ", default: Data(bytes: [\(bytes.joined(separator: ", "))])"
defaultString = ", initial: Data(bytes: [\(bytes.joined(separator: ", "))])"
}
case .transformableAttributeType:
if let attributeValueClassName = attribute.attributeValueClassName {
@@ -181,11 +172,11 @@ public extension DynamicSchema {
}
if let defaultValue = attribute.defaultValue {
defaultString = ", default: /* \"\(defaultValue)\" */"
defaultString = ", initial: /* \"\(defaultValue)\" */"
}
else if !attribute.isOptional {
defaultString = ", default: /* required */"
defaultString = ", initial: /* required */"
}
default:
fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)")

View File

@@ -78,26 +78,9 @@ 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, EmptyableAttributeType {
extension Bool: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -114,21 +97,11 @@ extension Bool: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Bool {
return false
}
}
// MARK: - CGFloat
extension CGFloat: ImportableAttributeType, EmptyableAttributeType {
extension CGFloat: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -145,21 +118,12 @@ extension CGFloat: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> CGFloat {
return 0
}
}
// MARK: - Data
extension Data: ImportableAttributeType, EmptyableAttributeType {
extension Data: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -176,15 +140,6 @@ extension Data: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Data {
return Data()
}
}
@@ -212,7 +167,7 @@ extension Date: ImportableAttributeType {
// MARK: - Double
extension Double: ImportableAttributeType, EmptyableAttributeType {
extension Double: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -229,21 +184,12 @@ extension Double: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Double {
return 0
}
}
// MARK: - Float
extension Float: ImportableAttributeType, EmptyableAttributeType {
extension Float: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -260,21 +206,12 @@ extension Float: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Float {
return 0
}
}
// MARK: - Int
extension Int: ImportableAttributeType, EmptyableAttributeType {
extension Int: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -291,21 +228,12 @@ extension Int: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Int {
return 0
}
}
// MARK: - Int8
extension Int8: ImportableAttributeType, EmptyableAttributeType {
extension Int8: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -322,21 +250,12 @@ extension Int8: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Int8 {
return 0
}
}
// MARK: - Int16
extension Int16: ImportableAttributeType, EmptyableAttributeType {
extension Int16: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -353,21 +272,12 @@ extension Int16: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Int16 {
return 0
}
}
// MARK: - Int32
extension Int32: ImportableAttributeType, EmptyableAttributeType {
extension Int32: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -384,21 +294,12 @@ extension Int32: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Int32 {
return 0
}
}
// MARK: - Int64
extension Int64: ImportableAttributeType, EmptyableAttributeType {
extension Int64: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -415,21 +316,12 @@ extension Int64: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Int64 {
return 0
}
}
// MARK: - NSData
extension NSData: ImportableAttributeType, EmptyableAttributeType {
extension NSData: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -446,15 +338,6 @@ extension NSData: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@nonobjc @inline(__always)
public class func cs_emptyValue() -> Self {
return self.init()
}
}
@@ -482,7 +365,7 @@ extension NSDate: ImportableAttributeType {
// MARK: - NSNumber
extension NSNumber: ImportableAttributeType, EmptyableAttributeType {
extension NSNumber: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -499,21 +382,12 @@ extension NSNumber: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@nonobjc @inline(__always)
public class func cs_emptyValue() -> Self {
return self.init()
}
}
// MARK: - NSString
extension NSString: ImportableAttributeType, EmptyableAttributeType {
extension NSString: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -530,15 +404,6 @@ extension NSString: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@nonobjc @inline(__always)
public class func cs_emptyValue() -> Self {
return self.init()
}
}
@@ -588,7 +453,7 @@ extension NSUUID: ImportableAttributeType {
// MARK: - String
extension String: ImportableAttributeType, EmptyableAttributeType {
extension String: ImportableAttributeType {
// MARK: ImportableAttributeType
@@ -605,15 +470,6 @@ extension String: ImportableAttributeType, EmptyableAttributeType {
return self.cs_toQueryableNativeType()
}
// MARK: EmptyableAttributeType
@inline(__always)
public static func cs_emptyValue() -> String {
return ""
}
}

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>4.0.5</string>
<string>4.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@@ -97,8 +97,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
public convenience init(
_ keyPath: KeyPath,
deleteRule: DeleteRule = .nullify,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -132,8 +132,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
_ keyPath: KeyPath,
inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -167,8 +167,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
_ keyPath: KeyPath,
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -202,8 +202,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
_ keyPath: KeyPath,
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -242,8 +242,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
internal let minCount: Int = 0
internal let maxCount: Int = 1
internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?)
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
@@ -294,7 +294,7 @@ public enum RelationshipContainer<O: CoreStoreObject> {
// MARK: Private
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
self.keyPath = keyPath
self.deleteRule = deleteRule.nativeValue
@@ -345,8 +345,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
minCount: Int = 0,
maxCount: Int = 0,
deleteRule: DeleteRule = .nullify,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -386,8 +386,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
maxCount: Int = 0,
inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -427,8 +427,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
maxCount: Int = 0,
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -468,8 +468,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
maxCount: Int = 0,
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -511,8 +511,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
internal let minCount: Int
internal let maxCount: Int
internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?)
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
@@ -563,7 +563,7 @@ public enum RelationshipContainer<O: CoreStoreObject> {
// MARK: Private
private init(keyPath: String, minCount: Int, maxCount: Int, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
private init(keyPath: String, minCount: Int, maxCount: Int, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
self.keyPath = keyPath
self.deleteRule = deleteRule.nativeValue
@@ -619,8 +619,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
deleteRule: DeleteRule = .nullify,
minCount: Int = 0,
maxCount: Int = 0,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -660,8 +660,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
deleteRule: DeleteRule = .nullify,
minCount: Int = 0,
maxCount: Int = 0,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -701,8 +701,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
deleteRule: DeleteRule = .nullify,
minCount: Int = 0,
maxCount: Int = 0,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -742,8 +742,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
deleteRule: DeleteRule = .nullify,
minCount: Int = 0,
maxCount: Int = 0,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
@@ -785,8 +785,8 @@ public enum RelationshipContainer<O: CoreStoreObject> {
internal let minCount: Int
internal let maxCount: Int
internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?)
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
@@ -837,7 +837,7 @@ public enum RelationshipContainer<O: CoreStoreObject> {
// MARK: Private
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
self.keyPath = keyPath
self.deleteRule = deleteRule.nativeValue
@@ -1204,21 +1204,3 @@ extension RelationshipContainer.ToManyUnordered {
return relationship.nativeValue.isEqual(relationship2.nativeValue)
}
}
// MARK: - RelationshipProtocol
internal protocol RelationshipProtocol: class {
var keyPath: KeyPath { get }
var isToMany: Bool { get }
var isOrdered: Bool { get }
var deleteRule: NSDeleteRule { get }
var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get }
var affectedByKeyPaths: () -> Set<String> { get }
weak var parentObject: CoreStoreObject? { get set }
var versionHashModifier: String? { get }
var renamingIdentifier: String? { get }
var minCount: Int { get }
var maxCount: Int { get }
}

View File

@@ -0,0 +1,45 @@
//
// RelationshipProtocol.swift
// CoreStore
//
// Copyright © 2017 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: - RelationshipProtocol
internal protocol RelationshipProtocol: class {
var keyPath: KeyPath { get }
var isToMany: Bool { get }
var isOrdered: Bool { get }
var deleteRule: NSDeleteRule { get }
var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get }
var affectedByKeyPaths: () -> Set<String> { get }
weak var parentObject: CoreStoreObject? { get set }
var versionHashModifier: () -> String? { get }
var renamingIdentifier: () -> String? { get }
var minCount: Int { get }
var maxCount: Int { get }
}

597
Sources/Transformable.swift Normal file
View File

@@ -0,0 +1,597 @@
//
// Transformable.swift
// CoreStore
//
// Copyright © 2017 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: - DynamicObject
public extension DynamicObject where Self: CoreStoreObject {
/**
The containing type for transformable properties. `Transformable` properties support types that conforms to `NSCoding & NSCopying`.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public typealias Transformable = TransformableContainer<Self>
}
// MARK: - TransformableContainer
/**
The containing type for transformable properties. Use the `DynamicObject.Transformable` typealias instead for shorter syntax.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
*/
public enum TransformableContainer<O: CoreStoreObject> {
// MARK: - Required
/**
The containing type for transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public final class Required<V: NSCoding & NSCopying>: AttributeProtocol {
/**
Initializes the metadata for the property.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let color = Transformable.Required<UIColor>(
"color",
initial: UIColor.clear,
isTransient: true,
customGetter: Animal.getColor(_:)
)
}
private static func getColor(_ partialObject: PartialObject<Animal>) -> UIColor {
let cachedColor = partialObject.primitiveValue(for: { $0.color })
if cachedColor != UIColor.clear {
return cachedColor
}
let color: UIColor
switch partialObject.value(for: { $0.species }) {
case "Swift": color = UIColor.orange
case "Bulbasaur": color = UIColor.green
default: color = UIColor.black
}
partialObject.setPrimitiveValue(color, for: { $0.color })
return color
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter initial: 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 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 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: KeyPath,
initial: @autoclosure @escaping () -> V,
isIndexed: Bool = false,
isTransient: Bool = false,
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<String> = []) {
self.keyPath = keyPath
self.defaultValue = initial
self.isIndexed = isIndexed
self.isTransient = isTransient
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
self.affectedByKeyPaths = affectedByKeyPaths
}
/**
The property value.
*/
public var value: V {
get {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
if let customGetter = self.customGetter {
return customGetter(PartialObject<O>(object.rawObject!))
}
return object.rawObject!.value(forKey: self.keyPath)! as! V
}
}
set {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
if let customSetter = self.customSetter {
return customSetter(PartialObject<O>(object.rawObject!), newValue)
}
object.rawObject!.setValue(
newValue,
forKey: self.keyPath
)
}
}
}
// MARK: AttributeProtocol
internal static var attributeType: NSAttributeType {
return .transformableAttributeType
}
public let keyPath: KeyPath
internal let isOptional = false
internal let isIndexed: Bool
internal let isTransient: Bool
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let defaultValue: () -> Any?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in
guard let customGetter = self.customGetter else {
return nil
}
let keyPath = self.keyPath
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 private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in
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: Private
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
// MARK: Deprecated
@available(*, deprecated: 3.1, renamed: "init(_:initial:isIndexed:isTransient:versionHashModifier:renamingIdentifier:customGetter:customSetter:affectedByKeyPaths:)")
public convenience init(
_ keyPath: KeyPath,
`default`: @autoclosure @escaping () -> V,
isIndexed: Bool = false,
isTransient: Bool = false,
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<String> = []) {
self.init(
keyPath,
initial: `default`,
isIndexed: isIndexed,
isTransient: isTransient,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
customGetter: customGetter,
customSetter: customSetter,
affectedByKeyPaths: affectedByKeyPaths
)
}
}
// MARK: - Optional
/**
The containing type for optional transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public final class Optional<V: NSCoding & NSCopying>: AttributeProtocol {
/**
Initializes the metadata for the property.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let color = Transformable.Optional<UIColor>(
"color",
isTransient: true,
customGetter: Animal.getColor(_:)
)
}
private static func getColor(_ partialObject: PartialObject<Animal>) -> UIColor? {
if let cachedColor = partialObject.primitiveValue(for: { $0.color }) {
return cachedColor
}
let color: UIColor?
switch partialObject.value(for: { $0.species }) {
case "Swift": color = UIColor.orange
case "Bulbasaur": color = UIColor.green
default: return nil
}
partialObject.setPrimitiveValue(color, for: { $0.color })
return color
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter initial: 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 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 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: KeyPath,
initial: @autoclosure @escaping () -> V? = nil,
isIndexed: Bool = false,
isTransient: Bool = false,
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<String> = []) {
self.keyPath = keyPath
self.defaultValue = initial
self.isIndexed = isIndexed
self.isTransient = isTransient
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
self.affectedByKeyPaths = affectedByKeyPaths
}
/**
The property value.
*/
public var value: V? {
get {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
if let customGetter = self.customGetter {
return customGetter(PartialObject<O>(object.rawObject!))
}
return object.rawObject!.value(forKey: self.keyPath) as! V?
}
}
set {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
if let customSetter = self.customSetter {
return customSetter(PartialObject<O>(object.rawObject!), newValue)
}
object.rawObject!.setValue(
newValue,
forKey: self.keyPath
)
}
}
}
// MARK: AttributeProtocol
internal static var attributeType: NSAttributeType {
return .transformableAttributeType
}
public let keyPath: KeyPath
internal let isOptional = true
internal let isIndexed: Bool
internal let isTransient: Bool
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let defaultValue: () -> Any?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in
guard let customGetter = self.customGetter else {
return nil
}
let keyPath = self.keyPath
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 private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in
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: Private
private let customGetter: ((_ partialObject: PartialObject<O>) -> V?)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)?
// MARK: Deprecated
@available(*, deprecated: 3.1, renamed: "init(_:initial:isIndexed:isTransient:versionHashModifier:renamingIdentifier:customGetter:customSetter:affectedByKeyPaths:)")
public convenience init(
_ keyPath: KeyPath,
`default`: @autoclosure @escaping () -> V?,
isIndexed: Bool = false,
isTransient: Bool = false,
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<String> = []) {
self.init(
keyPath,
initial: `default`,
isIndexed: isIndexed,
isTransient: isTransient,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
customGetter: customGetter,
customSetter: customSetter,
affectedByKeyPaths: affectedByKeyPaths
)
}
}
}
// MARK: - Operations
infix operator .= : AssignmentPrecedence
infix operator .== : ComparisonPrecedence
extension TransformableContainer.Required {
/**
Assigns a transformable value to the property. The operation
```
animal.color .= UIColor.red
```
is equivalent to
```
animal.color.value = UIColor.red
```
*/
public static func .= (_ property: TransformableContainer<O>.Required<V>, _ newValue: V) {
property.value = newValue
}
/**
Assigns a transformable value from another property. The operation
```
animal.nickname .= anotherAnimal.species
```
is equivalent to
```
animal.nickname.value = anotherAnimal.species.value
```
*/
public static func .= <O2>(_ property: TransformableContainer<O>.Required<V>, _ property2: TransformableContainer<O2>.Required<V>) {
property.value = property2.value
}
}
extension TransformableContainer.Optional {
/**
Assigns an optional transformable value to the property. The operation
```
animal.color .= UIColor.red
```
is equivalent to
```
animal.color.value = UIColor.red
```
*/
public static func .= (_ property: TransformableContainer<O>.Optional<V>, _ newValue: V?) {
property.value = newValue
}
/**
Assigns an optional transformable value from another property. The operation
```
animal.color .= anotherAnimal.color
```
is equivalent to
```
animal.color.value = anotherAnimal.color.value
```
*/
public static func .= <O2>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Optional<V>) {
property.value = property2.value
}
/**
Assigns a transformable value from another property. The operation
```
animal.color .= anotherAnimal.color
```
is equivalent to
```
animal.color.value = anotherAnimal.color.value
```
*/
public static func .= <O2>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Required<V>) {
property.value = property2.value
}
}

View File

@@ -43,19 +43,6 @@ public extension DynamicObject where Self: CoreStoreObject {
- Important: `Value` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public typealias Value = ValueContainer<Self>
/**
The containing type for transformable properties. `Transformable` properties support types that conforms to `NSCoding & NSCopying`.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public typealias Transformable = TransformableContainer<Self>
}
@@ -92,7 +79,7 @@ public enum ValueContainer<O: CoreStoreObject> {
Initializes the metadata for the property.
```
class Person: CoreStoreObject {
let title = Value.Required<String>("title", default: "Mr.")
let title = Value.Required<String>("title", initial: "Mr.")
let name = Value.Required<String>("name")
let displayName = Value.Required<String>(
"displayName",
@@ -114,7 +101,7 @@ public enum ValueContainer<O: CoreStoreObject> {
}
```
- parameter keyPath: the permanent attribute name for this property.
- 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 initial: the initial value for the property when the object is first created
- 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.)
@@ -125,11 +112,11 @@ public enum ValueContainer<O: CoreStoreObject> {
*/
public init(
_ keyPath: KeyPath,
`default`: @autoclosure @escaping () -> V,
initial: @autoclosure @escaping () -> V,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
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<String> = []) {
@@ -137,7 +124,7 @@ public enum ValueContainer<O: CoreStoreObject> {
self.keyPath = keyPath
self.isIndexed = isIndexed
self.isTransient = isTransient
self.defaultValue = { `default`().cs_toImportableNativeType() }
self.defaultValue = { initial().cs_toImportableNativeType() }
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
@@ -212,8 +199,8 @@ public enum ValueContainer<O: CoreStoreObject> {
internal let isOptional = false
internal let isIndexed: Bool
internal let isTransient: Bool
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let defaultValue: () -> Any?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
@@ -265,6 +252,34 @@ public enum ValueContainer<O: CoreStoreObject> {
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
// MARK: Deprecated
@available(*, deprecated: 3.1, renamed: "init(_:initial:isIndexed:isTransient:versionHashModifier:renamingIdentifier:customGetter:customSetter:affectedByKeyPaths:)")
public convenience init(
_ keyPath: KeyPath,
`default`: @autoclosure @escaping () -> V,
isIndexed: Bool = false,
isTransient: Bool = false,
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<String> = []) {
self.init(
keyPath,
initial: `default`,
isIndexed: isIndexed,
isTransient: isTransient,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
customGetter: customGetter,
customSetter: customSetter,
affectedByKeyPaths: affectedByKeyPaths
)
}
}
@@ -287,7 +302,7 @@ public enum ValueContainer<O: CoreStoreObject> {
Initializes the metadata for the property.
```
class Person: CoreStoreObject {
let title = Value.Optional<String>("title", default: "Mr.")
let title = Value.Optional<String>("title", initial: "Mr.")
let name = Value.Optional<String>("name")
let displayName = Value.Optional<String>(
"displayName",
@@ -308,7 +323,7 @@ public enum ValueContainer<O: CoreStoreObject> {
}
```
- 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 `nil` if not specified.
- parameter initial: 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 properties are unchanged but the format or content of its data are changed.)
@@ -324,11 +339,11 @@ public enum ValueContainer<O: CoreStoreObject> {
*/
public init(
_ keyPath: KeyPath,
`default`: @autoclosure @escaping () -> V? = nil,
initial: @autoclosure @escaping () -> V? = nil,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
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<String> = []) {
@@ -336,7 +351,7 @@ public enum ValueContainer<O: CoreStoreObject> {
self.keyPath = keyPath
self.isIndexed = isIndexed
self.isTransient = isTransient
self.defaultValue = { `default`()?.cs_toImportableNativeType() }
self.defaultValue = { initial()?.cs_toImportableNativeType() }
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
@@ -409,8 +424,8 @@ public enum ValueContainer<O: CoreStoreObject> {
internal let isOptional = true
internal let isIndexed: Bool
internal let isTransient: Bool
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let defaultValue: () -> Any?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
@@ -462,457 +477,34 @@ public enum ValueContainer<O: CoreStoreObject> {
private let customGetter: ((_ partialObject: PartialObject<O>) -> V?)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)?
}
}
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<String>("title", default: "Mr.") // explicit default value
let name = Value.Required<String>("name") // 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 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 convenience init(
_ keyPath: KeyPath,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
keyPath,
default: V.cs_emptyValue(),
isIndexed: isIndexed,
isTransient: isTransient,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
customGetter: customGetter,
customSetter: customSetter,
affectedByKeyPaths: affectedByKeyPaths
)
}
}
// MARK: - TransformableContainer
/**
The containing type for transformable properties. Use the `DynamicObject.Transformable` typealias instead for shorter syntax.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
*/
public enum TransformableContainer<O: CoreStoreObject> {
// MARK: - Required
/**
The containing type for transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public final class Required<V: NSCoding & NSCopying>: AttributeProtocol {
/**
Initializes the metadata for the property.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let color = Transformable.Required<UIColor>(
"color",
default: UIColor.clear,
isTransient: true,
customGetter: Animal.getColor(_:)
)
}
private static func getColor(_ partialObject: PartialObject<Animal>) -> UIColor {
let cachedColor = partialObject.primitiveValue(for: { $0.color })
if cachedColor != UIColor.clear {
return cachedColor
}
let color: UIColor
switch partialObject.value(for: { $0.species }) {
case "Swift": color = UIColor.orange
case "Bulbasaur": color = UIColor.green
default: color = UIColor.black
}
partialObject.setPrimitiveValue(color, for: { $0.color })
return color
}
```
- parameter keyPath: the permanent attribute name for this property.
- 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 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 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(
// MARK: Deprecated
@available(*, deprecated: 3.1, renamed: "init(_:initial:isIndexed:isTransient:versionHashModifier:renamingIdentifier:customGetter:customSetter:affectedByKeyPaths:)")
public convenience init(
_ keyPath: KeyPath,
`default`: @autoclosure @escaping () -> V,
`default`: @autoclosure @escaping () -> V?,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.keyPath = keyPath
self.defaultValue = `default`
self.isIndexed = isIndexed
self.isTransient = isTransient
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
self.affectedByKeyPaths = affectedByKeyPaths
}
/**
The property value.
*/
public var value: V {
get {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
if let customGetter = self.customGetter {
return customGetter(PartialObject<O>(object.rawObject!))
}
return object.rawObject!.value(forKey: self.keyPath)! as! V
}
}
set {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
if let customSetter = self.customSetter {
return customSetter(PartialObject<O>(object.rawObject!), newValue)
}
object.rawObject!.setValue(
newValue,
forKey: self.keyPath
)
}
}
}
// MARK: AttributeProtocol
internal static var attributeType: NSAttributeType {
return .transformableAttributeType
}
public let keyPath: KeyPath
internal let isOptional = false
internal let isIndexed: Bool
internal let isTransient: Bool
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal let defaultValue: () -> Any?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in
guard let customGetter = self.customGetter else {
return nil
}
let keyPath = self.keyPath
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 private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in
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: Private
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
}
// MARK: - Optional
/**
The containing type for optional transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public final class Optional<V: NSCoding & NSCopying>: AttributeProtocol {
/**
Initializes the metadata for the property.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let color = Transformable.Optional<UIColor>(
"color",
isTransient: true,
customGetter: Animal.getColor(_:)
)
}
private static func getColor(_ partialObject: PartialObject<Animal>) -> UIColor? {
if let cachedColor = partialObject.primitiveValue(for: { $0.color }) {
return cachedColor
}
let color: UIColor?
switch partialObject.value(for: { $0.species }) {
case "Swift": color = UIColor.orange
case "Bulbasaur": color = UIColor.green
default: return nil
}
partialObject.setPrimitiveValue(color, for: { $0.color })
return color
}
```
- parameter keyPath: the permanent attribute name for this property.
- 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 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 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: KeyPath,
`default`: @autoclosure @escaping () -> V? = nil,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: String? = nil,
renamingIdentifier: String? = nil,
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<String> = []) {
self.keyPath = keyPath
self.defaultValue = `default`
self.isIndexed = isIndexed
self.isTransient = isTransient
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
self.affectedByKeyPaths = affectedByKeyPaths
self.init(
keyPath,
initial: `default`,
isIndexed: isIndexed,
isTransient: isTransient,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
customGetter: customGetter,
customSetter: customSetter,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
The property value.
*/
public var value: V? {
get {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
if let customGetter = self.customGetter {
return customGetter(PartialObject<O>(object.rawObject!))
}
return object.rawObject!.value(forKey: self.keyPath) as! V?
}
}
set {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
if let customSetter = self.customSetter {
return customSetter(PartialObject<O>(object.rawObject!), newValue)
}
object.rawObject!.setValue(
newValue,
forKey: self.keyPath
)
}
}
}
// MARK: AttributeProtocol
internal static var attributeType: NSAttributeType {
return .transformableAttributeType
}
public let keyPath: KeyPath
internal let isOptional = true
internal let isIndexed: Bool
internal let isTransient: Bool
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal let defaultValue: () -> Any?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in
guard let customGetter = self.customGetter else {
return nil
}
let keyPath = self.keyPath
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 private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in
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: Private
private let customGetter: ((_ partialObject: PartialObject<O>) -> V?)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)?
}
}
@@ -1122,104 +714,3 @@ extension ValueContainer.Optional {
return property.value == property2.value
}
}
extension TransformableContainer.Required {
/**
Assigns a transformable value to the property. The operation
```
animal.color .= UIColor.red
```
is equivalent to
```
animal.color.value = UIColor.red
```
*/
public static func .= (_ property: TransformableContainer<O>.Required<V>, _ newValue: V) {
property.value = newValue
}
/**
Assigns a transformable value from another property. The operation
```
animal.nickname .= anotherAnimal.species
```
is equivalent to
```
animal.nickname.value = anotherAnimal.species.value
```
*/
public static func .= <O2: CoreStoreObject>(_ property: TransformableContainer<O>.Required<V>, _ property2: TransformableContainer<O2>.Required<V>) {
property.value = property2.value
}
}
extension TransformableContainer.Optional {
/**
Assigns an optional transformable value to the property. The operation
```
animal.color .= UIColor.red
```
is equivalent to
```
animal.color.value = UIColor.red
```
*/
public static func .= (_ property: TransformableContainer<O>.Optional<V>, _ newValue: V?) {
property.value = newValue
}
/**
Assigns an optional transformable value from another property. The operation
```
animal.color .= anotherAnimal.color
```
is equivalent to
```
animal.color.value = anotherAnimal.color.value
```
*/
public static func .= <O2: CoreStoreObject>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Optional<V>) {
property.value = property2.value
}
/**
Assigns a transformable value from another property. The operation
```
animal.color .= anotherAnimal.color
```
is equivalent to
```
animal.color.value = anotherAnimal.color.value
```
*/
public static func .= <O2: CoreStoreObject>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Required<V>) {
property.value = property2.value
}
}
// MARK: - AttributeProtocol
internal protocol AttributeProtocol: class {
static var attributeType: NSAttributeType { get }
var keyPath: KeyPath { get }
var isOptional: Bool { get }
var isIndexed: Bool { get }
var isTransient: Bool { get }
var versionHashModifier: String? { get }
var renamingIdentifier: String? { get }
var defaultValue: () -> Any? { get }
var affectedByKeyPaths: () -> Set<String> { get }
weak var parentObject: CoreStoreObject? { get set }
var getter: CoreStoreManagedObject.CustomGetter? { get }
var setter: CoreStoreManagedObject.CustomSetter? { get }
}