From 56d0ea46ea84048a62703708e1e2e81b8dd9e92a Mon Sep 17 00:00:00 2001 From: John Estropia Date: Sat, 23 May 2020 12:07:16 +0900 Subject: [PATCH] Implement dynamic initializers for Field properties (fixes #382) --- .../CollectionViewDemoViewController.swift | 7 +- .../ColorsDemo.swift | 12 +- .../ListObserverDemoViewController.swift | 7 +- .../ObjectObserverDemoViewController.swift | 27 ++- .../Palette.swift | 89 +++++---- .../SwiftUIContainerViewController.swift | 4 +- .../SwiftUI Demo/SwiftUIView.swift | 11 +- Sources/CoreStoreManagedObject.swift | 1 + Sources/CoreStoreSchema.swift | 127 +++++++++--- Sources/Field.Coded.swift | 186 +++++++++++++++++- Sources/Field.Stored.swift | 77 +++++++- Sources/Field.Virtual.swift | 2 + Sources/FieldAttributeProtocol.swift | 1 + Sources/From+Querying.swift | 24 +++ .../NSEntityDescription+DynamicModel.swift | 22 --- 15 files changed, 455 insertions(+), 142 deletions(-) diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/CollectionViewDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/CollectionViewDemoViewController.swift index 4fe5637..8ff77b5 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/CollectionViewDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/CollectionViewDemoViewController.swift @@ -146,8 +146,7 @@ final class CollectionViewDemoViewController: UICollectionViewController { ColorsDemo.stack.perform( asynchronous: { (transaction) in - let palette = transaction.create(Into()) - palette.setInitialValues(in: transaction) + _ = transaction.create(Into()) }, completion: { _ in } ) @@ -159,8 +158,8 @@ final class CollectionViewDemoViewController: UICollectionViewController { for palette in try transaction.fetchAll(From()) { - palette.hue .= Palette.randomHue() - palette.colorName .= nil + palette.hue = Palette.randomHue() + palette.colorName = nil } }, completion: { _ in } diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ColorsDemo.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ColorsDemo.swift index 0a5d837..a3d182b 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ColorsDemo.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ColorsDemo.swift @@ -35,8 +35,8 @@ struct ColorsDemo { switch self { case .all: return .init() - case .light: return (\Palette.brightness >= 0.9) - case .dark: return (\Palette.brightness <= 0.4) + case .light: return (\Palette.$brightness >= 0.9) + case .dark: return (\Palette.$brightness <= 0.4) } } } @@ -47,9 +47,9 @@ struct ColorsDemo { try! self.palettes.refetch( From() - .sectionBy(\.colorName) + .sectionBy(\.$colorName) .where(self.filter.whereClause()) - .orderBy(.ascending(\.hue)) + .orderBy(.ascending(\.$hue)) ) } } @@ -81,8 +81,8 @@ struct ColorsDemo { return ColorsDemo.stack.publishList( From() - .sectionBy(\.colorName) - .orderBy(.ascending(\.hue)) + .sectionBy(\.$colorName) + .orderBy(.ascending(\.$hue)) ) }() } diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift index f1b1907..0d4f341 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift @@ -158,8 +158,7 @@ final class ListObserverDemoViewController: UITableViewController { ColorsDemo.stack.perform( asynchronous: { (transaction) in - let palette = transaction.create(Into()) - palette.setInitialValues(in: transaction) + _ = transaction.create(Into()) }, completion: { _ in } ) @@ -171,8 +170,8 @@ final class ListObserverDemoViewController: UITableViewController { for palette in try transaction.fetchAll(From()) { - palette.hue .= Palette.randomHue() - palette.colorName .= nil + palette.hue = Palette.randomHue() + palette.colorName = nil } }, completion: { _ in } diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift index 6ab1fa2..68fd561 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift @@ -43,7 +43,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { required init?(coder aDecoder: NSCoder) { - if let palette = try! ColorsDemo.stack.fetchOne(From().orderBy(.ascending(\.hue))) { + if let palette = try! ColorsDemo.stack.fetchOne(From().orderBy(.ascending(\.$hue))) { self.monitor = ColorsDemo.stack.monitorObject(palette) } @@ -52,12 +52,11 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { _ = try? ColorsDemo.stack.perform( synchronous: { (transaction) in - let palette = transaction.create(Into()) - palette.setInitialValues(in: transaction) + _ = transaction.create(Into()) } ) - let palette = try! ColorsDemo.stack.fetchOne(From().orderBy(.ascending(\.hue)))! + let palette = try! ColorsDemo.stack.fetchOne(From().orderBy(.ascending(\.$hue)))! self.monitor = ColorsDemo.stack.monitorObject(palette) } @@ -119,7 +118,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { if let palette = transaction.edit(self?.monitor?.object) { - palette.hue .= Int(hue) + palette.hue = Int(hue) } }, completion: { _ in } @@ -134,7 +133,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { if let palette = transaction.edit(self?.monitor?.object) { - palette.saturation .= saturation + palette.saturation = saturation } }, completion: { _ in } @@ -149,7 +148,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { if let palette = transaction.edit(self?.monitor?.object) { - palette.brightness .= brightness + palette.brightness = brightness } }, completion: { _ in } @@ -169,7 +168,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { func reloadPaletteInfo(_ palette: Palette, changedKeys: Set?) { - self.colorNameLabel?.text = palette.colorName.value + self.colorNameLabel?.text = palette.colorName let color = palette.color self.colorNameLabel?.textColor = color @@ -177,17 +176,17 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { self.hsbLabel?.text = palette.colorText - if changedKeys == nil || changedKeys?.contains(String(keyPath: \Palette.hue)) == true { + if changedKeys == nil || changedKeys?.contains(String(keyPath: \Palette.$hue)) == true { - self.hueSlider?.value = Float(palette.hue.value) + self.hueSlider?.value = Float(palette.hue) } - if changedKeys == nil || changedKeys?.contains(String(keyPath: \Palette.saturation)) == true { + if changedKeys == nil || changedKeys?.contains(String(keyPath: \Palette.$saturation)) == true { - self.saturationSlider?.value = palette.saturation.value + self.saturationSlider?.value = palette.saturation } - if changedKeys == nil || changedKeys?.contains(String(keyPath: \Palette.brightness)) == true { + if changedKeys == nil || changedKeys?.contains(String(keyPath: \Palette.$brightness)) == true { - self.brightnessSlider?.value = palette.brightness.value + self.brightnessSlider?.value = palette.brightness } } } diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift index 688b1ee..3d05cad 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift @@ -16,44 +16,53 @@ import CoreStore final class Palette: CoreStoreObject { - let hue = Value.Required("hue", initial: 0) - let saturation = Value.Required("saturation", initial: 0) - let brightness = Value.Required("brightness", initial: 0) - - let colorName = Value.Optional( - "colorName", - isTransient: true, - customGetter: Palette.getColorName + @Field.Stored( + "hue", + dynamicInitialValue: { Palette.randomHue() } ) + var hue: Int - static func randomHue() -> Int { - - return Int(arc4random_uniform(360)) - } + @Field.Stored("saturation") + var saturation: Float = 1 - private static func getColorName(_ partialObject: PartialObject) -> String? { - - if let colorName = partialObject.primitiveValue(for: { $0.colorName }) { - + @Field.Stored( + "brightness", + dynamicInitialValue: { Palette.randomBrightness() } + ) + var brightness: Float + + @Field.Virtual( + "colorName", + customGetter: { object, field in + if let colorName = field.primitiveValue { + return colorName + } + let colorName: String + switch object.$hue.value % 360 { + case 0 ..< 20: colorName = "Lower Reds" + case 20 ..< 57: colorName = "Oranges and Browns" + case 57 ..< 90: colorName = "Yellow-Greens" + case 90 ..< 159: colorName = "Greens" + case 159 ..< 197: colorName = "Blue-Greens" + case 197 ..< 241: colorName = "Blues" + case 241 ..< 297: colorName = "Violets" + case 297 ..< 331: colorName = "Magentas" + default: colorName = "Upper Reds" + } + field.primitiveValue = colorName return colorName } + ) + var colorName: String! + + static func randomHue() -> Int { - let colorName: String - switch partialObject.value(for: { $0.hue }) % 360 { - - case 0 ..< 20: colorName = "Lower Reds" - case 20 ..< 57: colorName = "Oranges and Browns" - case 57 ..< 90: colorName = "Yellow-Greens" - case 90 ..< 159: colorName = "Greens" - case 159 ..< 197: colorName = "Blue-Greens" - case 197 ..< 241: colorName = "Blues" - case 241 ..< 297: colorName = "Violets" - case 297 ..< 331: colorName = "Magentas" - default: colorName = "Upper Reds" - } + return Int.random(in: 0 ..< 360) + } + + static func randomBrightness() -> Float { - partialObject.setPrimitiveValue(colorName, for: { $0.colorName }) - return colorName + return (Float.random(in: 0 ..< 70) + 30) / 100.0 } } @@ -62,25 +71,15 @@ extension Palette { var color: UIColor { return UIColor( - hue: CGFloat(self.hue.value) / 360.0, - saturation: CGFloat(self.saturation.value), - brightness: CGFloat(self.brightness.value), + hue: CGFloat(self.hue) / 360.0, + saturation: CGFloat(self.saturation), + brightness: CGFloat(self.brightness), alpha: 1.0 ) } var colorText: String { - return "H: \(self.hue.value)˚, S: \(round(self.saturation.value * 100.0))%, B: \(round(self.brightness.value * 100.0))%" - } -} - -extension Palette { - - func setInitialValues(in transaction: BaseDataTransaction) { - - self.hue .= Palette.randomHue() - self.saturation .= Float(1.0) - self.brightness .= Float(arc4random_uniform(70) + 30) / 100.0 + return "H: \(self.hue)˚, S: \(round(self.saturation * 100.0))%, B: \(round(self.brightness * 100.0))%" } } diff --git a/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIContainerViewController.swift b/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIContainerViewController.swift index 5d78086..6933882 100644 --- a/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIContainerViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIContainerViewController.swift @@ -33,8 +33,8 @@ final class SwiftUIContainerViewController: UIViewController { rootView: SwiftUIView( palettes: ColorsDemo.stack.publishList( From() - .sectionBy(\.colorName) - .orderBy(.ascending(\.hue)) + .sectionBy(\.$colorName) + .orderBy(.ascending(\.$hue)) ) ) .environment(\.dataStack, ColorsDemo.stack) diff --git a/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift b/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift index 1d8c334..764119c 100644 --- a/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift +++ b/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift @@ -61,8 +61,8 @@ struct SwiftUIView: View { for palette in try transaction.fetchAll(From()) { - palette.hue .= Palette.randomHue() - palette.colorName .= nil + palette.hue = Palette.randomHue() + palette.colorName = nil } }, completion: { _ in } @@ -79,8 +79,7 @@ struct SwiftUIView: View { self.dataStack.perform( asynchronous: { transaction in - let palette = transaction.create(Into()) - palette.setInitialValues(in: transaction) + _ = transaction.create(Into()) }, completion: { _ in } ) @@ -173,8 +172,8 @@ struct SwiftUIView_Previews: PreviewProvider { SwiftUIView( palettes: ColorsDemo.stack.publishList( From() - .sectionBy(\.colorName) - .orderBy(.ascending(\.hue)) + .sectionBy(\.$colorName) + .orderBy(.ascending(\.$hue)) ) ) .environment(\.dataStack, ColorsDemo.stack) diff --git a/Sources/CoreStoreManagedObject.swift b/Sources/CoreStoreManagedObject.swift index 37f1204..896fc02 100644 --- a/Sources/CoreStoreManagedObject.swift +++ b/Sources/CoreStoreManagedObject.swift @@ -33,6 +33,7 @@ import Foundation internal typealias CustomGetter = @convention(block) (_ rawObject: Any) -> Any? internal typealias CustomSetter = @convention(block) (_ rawObject: Any, _ newValue: Any?) -> Void + internal typealias CustomInitializer = @convention(block) (_ rawObject: Any) -> Void internal typealias CustomGetterSetter = (getter: CustomGetter?, setter: CustomSetter?) @nonobjc @inline(__always) diff --git a/Sources/CoreStoreSchema.swift b/Sources/CoreStoreSchema.swift index 2d61cd9..dda2d4a 100644 --- a/Sources/CoreStoreSchema.swift +++ b/Sources/CoreStoreSchema.swift @@ -209,15 +209,17 @@ public final class CoreStoreSchema: DynamicSchema { let rawModel = NSManagedObjectModel() var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:] var allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:] + var allCustomInitializers: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomInitializer]] = [:] var allFieldCoders: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:] for entity in self.allEntities { - let (entityDescription, customGetterSetterByKeyPaths, fieldCoders) = self.entityDescription( + let (entityDescription, customGetterSetterByKeyPaths, customInitializerByKeyPaths, fieldCoders) = self.entityDescription( for: entity, initializer: CoreStoreSchema.firstPassCreateEntityDescription(from:in:) ) entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription) allCustomGettersSetters[entity] = customGetterSetterByKeyPaths + allCustomInitializers[entity] = customInitializerByKeyPaths allFieldCoders[entity] = fieldCoders } CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity) @@ -225,6 +227,7 @@ public final class CoreStoreSchema: DynamicSchema { CoreStoreSchema.fourthPassSynthesizeManagedObjectClasses( for: entityDescriptionsByEntity, allCustomGettersSetters: allCustomGettersSetters, + allCustomInitializers: allCustomInitializers, allFieldCoders: allFieldCoders ) @@ -257,6 +260,7 @@ public final class CoreStoreSchema: DynamicSchema { private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:] private var customGettersSettersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:] + private var customInitializersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomInitializer]] = [:] private var fieldCodersByEntity: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:] private weak var cachedRawModel: NSManagedObjectModel? @@ -265,11 +269,13 @@ public final class CoreStoreSchema: DynamicSchema { initializer: (DynamicEntity, ModelVersion) -> ( entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter], + customInitializersByEntity: [KeyPathString: CoreStoreManagedObject.CustomInitializer], fieldCoders: [KeyPathString: Internals.AnyFieldCoder] ) ) -> ( entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter], + customInitializerByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomInitializer], fieldCoders: [KeyPathString: Internals.AnyFieldCoder] ) { @@ -278,23 +284,31 @@ public final class CoreStoreSchema: DynamicSchema { return ( cachedEntityDescription, self.customGettersSettersByEntity[entity] ?? [:], + self.customInitializersByEntity[entity] ?? [:], self.fieldCodersByEntity[entity] ?? [:] ) } let modelVersion = self.modelVersion - let (entityDescription, customGetterSetterByKeyPaths, fieldCoders) = withoutActuallyEscaping( + let (entityDescription, customGetterSetterByKeyPaths, customInitializerByKeyPaths, fieldCoders) = withoutActuallyEscaping( initializer, do: { $0(entity, modelVersion) } ) self.entityDescriptionsByEntity[entity] = entityDescription self.customGettersSettersByEntity[entity] = customGetterSetterByKeyPaths + self.customInitializersByEntity[entity] = customInitializerByKeyPaths self.fieldCodersByEntity[entity] = fieldCoders - return (entityDescription, customGetterSetterByKeyPaths, fieldCoders) + return ( + entityDescription, + customGetterSetterByKeyPaths, + customInitializerByKeyPaths, + fieldCoders + ) } private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> ( entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter], + customInitializerByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomInitializer], fieldCoders: [KeyPathString: Internals.AnyFieldCoder] ) { @@ -306,6 +320,7 @@ public final class CoreStoreSchema: DynamicSchema { entityDescription.managedObjectClassName = CoreStoreManagedObject.cs_subclassName(for: entity, in: modelVersion) var keyPathsByAffectedKeyPaths: [KeyPathString: Set] = [:] + var customInitialValuesByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomInitializer] = [:] var customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter] = [:] var fieldCoders: [KeyPathString: Internals.AnyFieldCoder] = [:] func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] { @@ -338,6 +353,7 @@ public final class CoreStoreSchema: DynamicSchema { keyPathsByAffectedKeyPaths[attribute.keyPath] = entityDescriptionValues.affectedByKeyPaths customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter) + customInitialValuesByKeyPaths[attribute.keyPath] = attribute.initializer fieldCoders[attribute.keyPath] = valueTransformer case let relationship as FieldRelationshipProtocol: @@ -401,7 +417,12 @@ public final class CoreStoreSchema: DynamicSchema { } entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type) entityDescription.keyPathsByAffectedKeyPaths = keyPathsByAffectedKeyPaths - return (entityDescription, customGetterSetterByKeyPaths, fieldCoders) + return ( + entityDescription, + customGetterSetterByKeyPaths, + customInitialValuesByKeyPaths, + fieldCoders + ) } private static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) { @@ -597,10 +618,15 @@ public final class CoreStoreSchema: DynamicSchema { private static func fourthPassSynthesizeManagedObjectClasses( for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription], allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]], + allCustomInitializers: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomInitializer]], allFieldCoders: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] ) { - func createManagedObjectSubclass(for entityDescription: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]?) { + func createManagedObjectSubclass( + for entityDescription: NSEntityDescription, + customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]?, + customInitializers: [KeyPathString: CoreStoreManagedObject.CustomInitializer]? + ) { let superEntity = entityDescription.superentity let className = entityDescription.managedObjectClassName! @@ -612,7 +638,8 @@ public final class CoreStoreSchema: DynamicSchema { createManagedObjectSubclass( for: superEntity, - customGetterSetterByKeyPaths: superEntity.coreStoreEntity.flatMap({ allCustomGettersSetters[$0] }) + customGetterSetterByKeyPaths: superEntity.coreStoreEntity.flatMap({ allCustomGettersSetters[$0] }), + customInitializers: superEntity.coreStoreEntity.flatMap({ allCustomInitializers[$0] }) ) } let superClass = Internals.with { () -> CoreStoreManagedObject.Type in @@ -675,40 +702,80 @@ public final class CoreStoreSchema: DynamicSchema { } } } - - let newSelector = NSSelectorFromString("cs_keyPathsForValuesAffectingValueForKey:") - let keyPathsByAffectedKeyPaths = entityDescription.keyPathsByAffectedKeyPaths - let keyPathsForValuesAffectingValue: @convention(block) (Any, String) -> Set = { (instance, keyPath) in - - if let keyPaths = keyPathsByAffectedKeyPaths[keyPath] { + swizzle_keyPathsForValuesAffectingValueForKey: do { + + let newSelector = NSSelectorFromString("cs_keyPathsForValuesAffectingValueForKey:") + let keyPathsByAffectedKeyPaths = entityDescription.keyPathsByAffectedKeyPaths + let keyPathsForValuesAffectingValue: @convention(block) (Any, String) -> Set = { (instance, keyPath) in - return keyPaths + if let keyPaths = keyPathsByAffectedKeyPaths[keyPath] { + + return keyPaths + } + return [] } - return [] - } - let origSelector = #selector(CoreStoreManagedObject.keyPathsForValuesAffectingValue(forKey:)) - - let metaClass: AnyClass = object_getClass(managedObjectClass)! - let origMethod = class_getClassMethod(managedObjectClass, origSelector)! - - let origImp = method_getImplementation(origMethod) - let newImp = imp_implementationWithBlock(keyPathsForValuesAffectingValue) - - if class_addMethod(metaClass, origSelector, newImp, method_getTypeEncoding(origMethod)) { + let origSelector = #selector(CoreStoreManagedObject.keyPathsForValuesAffectingValue(forKey:)) - class_replaceMethod(metaClass, newSelector, origImp, method_getTypeEncoding(origMethod)) - } - else { + let metaClass: AnyClass = object_getClass(managedObjectClass)! + let origMethod = class_getClassMethod(managedObjectClass, origSelector)! - let newMethod = class_getClassMethod(managedObjectClass, newSelector)! - method_exchangeImplementations(origMethod, newMethod) + let origImp = method_getImplementation(origMethod) + let newImp = imp_implementationWithBlock(keyPathsForValuesAffectingValue) + + if class_addMethod(metaClass, origSelector, newImp, method_getTypeEncoding(origMethod)) { + + class_replaceMethod(metaClass, newSelector, origImp, method_getTypeEncoding(origMethod)) + } + else { + + let newMethod = class_getClassMethod(managedObjectClass, newSelector)! + method_exchangeImplementations(origMethod, newMethod) + } + } + swizzle_awakeFromInsert: do { + + let newSelector = NSSelectorFromString("cs_awakeFromInsert") + let awakeFromInsertValue: @convention(block) (Any) -> Void + if let customInitializers = customInitializers, + !customInitializers.isEmpty { + + let initializers = Array(customInitializers.values) + awakeFromInsertValue = { (instance) in + + initializers.forEach { + + $0(instance) + } + } + } + else { + + awakeFromInsertValue = { _ in } + } + let origSelector = #selector(CoreStoreManagedObject.awakeFromInsert) + + let origMethod = class_getInstanceMethod(managedObjectClass, origSelector)! + + let origImp = method_getImplementation(origMethod) + let newImp = imp_implementationWithBlock(awakeFromInsertValue) + + if class_addMethod(managedObjectClass, origSelector, newImp, method_getTypeEncoding(origMethod)) { + + class_replaceMethod(managedObjectClass, newSelector, origImp, method_getTypeEncoding(origMethod)) + } + else { + + let newMethod = class_getInstanceMethod(managedObjectClass, newSelector)! + method_exchangeImplementations(origMethod, newMethod) + } } } for (dynamicEntity, entityDescription) in entityDescriptionsByEntity { createManagedObjectSubclass( for: entityDescription, - customGetterSetterByKeyPaths: allCustomGettersSetters[dynamicEntity] + customGetterSetterByKeyPaths: allCustomGettersSetters[dynamicEntity], + customInitializers: allCustomInitializers[dynamicEntity] ) } diff --git a/Sources/Field.Coded.swift b/Sources/Field.Coded.swift index d7db474..d17e8d7 100644 --- a/Sources/Field.Coded.swift +++ b/Sources/Field.Coded.swift @@ -94,6 +94,32 @@ extension FieldContainer { valueTransformer: { Internals.AnyFieldCoder(fieldCoderType) }, customGetter: customGetter, customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + public init( + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder fieldCoderType: Coder.Type, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [], + dynamicInitialValue: @escaping () -> V + ) where Coder.FieldStoredValue == V { + + self.init( + defaultValue: nil, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(fieldCoderType) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: dynamicInitialValue, affectedByKeyPaths: affectedByKeyPaths ) } @@ -142,6 +168,32 @@ extension FieldContainer { valueTransformer: { Internals.AnyFieldCoder(tag: UUID(), encode: coder.encode, decode: coder.decode) }, customGetter: customGetter, customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + public init( + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder: (encode: (V) -> Data?, decode: (Data?) -> V), + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [], + dynamicInitialValue: @escaping () -> V + ) { + + self.init( + defaultValue: nil, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(tag: UUID(), encode: coder.encode, decode: coder.decode) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: dynamicInitialValue, affectedByKeyPaths: affectedByKeyPaths ) } @@ -333,12 +385,29 @@ extension FieldContainer { ) } } + + internal var initializer: CoreStoreManagedObject.CustomInitializer? { + + guard let dynamicInitialValue = self.dynamicInitialValue else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any) -> Void in + + let rawObject = id as! CoreStoreManagedObject + rawObject.setPrimitiveValue( + dynamicInitialValue(), + forKey: keyPath + ) + } + } // MARK: FilePrivate fileprivate init( - defaultValue: @escaping () -> Any?, + defaultValue: (() -> Any?)?, keyPath: KeyPathString, isOptional: Bool, versionHashModifier: @escaping () -> String?, @@ -346,6 +415,7 @@ extension FieldContainer { valueTransformer: @escaping () -> Internals.AnyFieldCoder?, customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)?, customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? , + dynamicInitialValue: (() -> V)?, affectedByKeyPaths: @escaping () -> Set) { self.keyPath = keyPath @@ -361,14 +431,17 @@ extension FieldContainer { renamingIdentifier: renamingIdentifier(), valueTransformer: fieldCoder, affectedByKeyPaths: affectedByKeyPaths(), - defaultValue: Internals.AnyFieldCoder.TransformableDefaultValueCodingBox( - defaultValue: defaultValue(), - fieldCoder: fieldCoder - ) + defaultValue: defaultValue.map { + Internals.AnyFieldCoder.TransformableDefaultValueCodingBox( + defaultValue: $0(), + fieldCoder: fieldCoder + ) as Any + } ) } self.customGetter = customGetter self.customSetter = customSetter + self.dynamicInitialValue = dynamicInitialValue } @@ -376,6 +449,7 @@ extension FieldContainer { private let customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? private let customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? + private let dynamicInitialValue: (() -> V)? } } @@ -422,6 +496,32 @@ extension FieldContainer.Coded where V: FieldOptionalType { valueTransformer: { Internals.AnyFieldCoder(coder) }, customGetter: customGetter, customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + public init( + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder: Coder.Type, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [], + dynamicInitialValue: @escaping () -> V + ) where Coder.FieldStoredValue == V.Wrapped { + + self.init( + defaultValue: nil, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(coder) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: dynamicInitialValue, affectedByKeyPaths: affectedByKeyPaths ) } @@ -470,6 +570,32 @@ extension FieldContainer.Coded where V: FieldOptionalType { valueTransformer: { Internals.AnyFieldCoder(tag: UUID(), encode: coder.encode, decode: coder.decode) }, customGetter: customGetter, customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + public init( + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder: (encode: (V) -> Data?, decode: (Data?) -> V), + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [], + dynamicInitialValue: @escaping () -> V + ) { + + self.init( + defaultValue: nil, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(tag: UUID(), encode: coder.encode, decode: coder.decode) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: dynamicInitialValue, affectedByKeyPaths: affectedByKeyPaths ) } @@ -516,6 +642,31 @@ extension FieldContainer.Coded where V: DefaultNSSecureCodable { valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding.self) }, customGetter: customGetter, customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + public init( + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [], + dynamicInitialValue: @escaping () -> V + ) { + + self.init( + defaultValue: nil, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding.self) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: dynamicInitialValue, affectedByKeyPaths: affectedByKeyPaths ) } @@ -562,6 +713,31 @@ extension FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSS valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding.self) }, customGetter: customGetter, customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + public init( + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [], + dynamicInitialValue: @escaping () -> V + ) { + + self.init( + defaultValue: nil, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding.self) }, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: dynamicInitialValue, affectedByKeyPaths: affectedByKeyPaths ) } diff --git a/Sources/Field.Stored.swift b/Sources/Field.Stored.swift index c025d10..5ecc06f 100644 --- a/Sources/Field.Stored.swift +++ b/Sources/Field.Stored.swift @@ -85,6 +85,30 @@ extension FieldContainer { renamingIdentifier: previousVersionKeyPath, customGetter: customGetter, customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + public init( + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [], + dynamicInitialValue: @escaping () -> V + ) { + + self.init( + wrappedValue: nil, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: dynamicInitialValue, affectedByKeyPaths: affectedByKeyPaths ) } @@ -262,18 +286,36 @@ extension FieldContainer { ) } } + + internal var initializer: CoreStoreManagedObject.CustomInitializer? { + + guard let dynamicInitialValue = self.dynamicInitialValue else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any) -> Void in + + let rawObject = id as! CoreStoreManagedObject + rawObject.setPrimitiveValue( + dynamicInitialValue().cs_toFieldStoredNativeType(), + forKey: keyPath + ) + } + } // MARK: FilePrivate fileprivate init( - wrappedValue initial: @escaping () -> V, + wrappedValue initial: (() -> V)?, keyPath: KeyPathString, isOptional: Bool, versionHashModifier: @escaping () -> String?, renamingIdentifier: @escaping () -> String?, customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)?, - customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? , + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)?, + dynamicInitialValue: (() -> V)?, affectedByKeyPaths: @escaping () -> Set) { self.keyPath = keyPath @@ -287,11 +329,12 @@ extension FieldContainer { renamingIdentifier: renamingIdentifier(), valueTransformer: nil, affectedByKeyPaths: affectedByKeyPaths(), - defaultValue: initial().cs_toFieldStoredNativeType() + defaultValue: initial?().cs_toFieldStoredNativeType() ) } self.customGetter = customGetter self.customSetter = customSetter + self.dynamicInitialValue = dynamicInitialValue } @@ -299,6 +342,7 @@ extension FieldContainer { private let customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? private let customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? + private let dynamicInitialValue: (() -> V)? } } @@ -331,7 +375,8 @@ extension FieldContainer.Stored where V: FieldOptionalType { previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { self.init( wrappedValue: initial, @@ -341,6 +386,30 @@ extension FieldContainer.Stored where V: FieldOptionalType { renamingIdentifier: previousVersionKeyPath, customGetter: customGetter, customSetter: customSetter, + dynamicInitialValue: nil, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + public init( + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy) -> V)? = nil, + customSetter: ((_ object: ObjectProxy, _ field: ObjectProxy.FieldProxy, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [], + dynamicInitialValue: @escaping () -> V + ) { + + self.init( + wrappedValue: nil, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + customGetter: customGetter, + customSetter: customSetter, + dynamicInitialValue: dynamicInitialValue, affectedByKeyPaths: affectedByKeyPaths ) } diff --git a/Sources/Field.Virtual.swift b/Sources/Field.Virtual.swift index eb77f11..d9c9b7a 100644 --- a/Sources/Field.Virtual.swift +++ b/Sources/Field.Virtual.swift @@ -275,6 +275,8 @@ extension FieldContainer { ) } } + + let initializer: CoreStoreManagedObject.CustomInitializer? = nil // MARK: FilePrivate diff --git a/Sources/FieldAttributeProtocol.swift b/Sources/FieldAttributeProtocol.swift index a2df2bd..8aae3d5 100644 --- a/Sources/FieldAttributeProtocol.swift +++ b/Sources/FieldAttributeProtocol.swift @@ -48,4 +48,5 @@ internal protocol FieldAttributeProtocol: FieldProtocol { var entityDescriptionValues: () -> EntityDescriptionValues { get } var getter: CoreStoreManagedObject.CustomGetter? { get } var setter: CoreStoreManagedObject.CustomSetter? { get } + var initializer: CoreStoreManagedObject.CustomInitializer? { get } } diff --git a/Sources/From+Querying.swift b/Sources/From+Querying.swift index 82ee3e1..9bc8e99 100644 --- a/Sources/From+Querying.swift +++ b/Sources/From+Querying.swift @@ -345,6 +345,30 @@ extension From where O: CoreStoreObject { return self.select(R.self, [SelectTerm.attribute(keyPath)]) } + /** + Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections + + - parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections + - returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path + */ + @available(macOS 10.12, *) + public func sectionBy(_ sectionKeyPath: KeyPath.Stored>) -> SectionMonitorChainBuilder { + + return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 }) + } + + /** + Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections + + - parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections + - returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path + */ + @available(macOS 10.12, *) + public func sectionBy(_ sectionKeyPath: KeyPath.Virtual>) -> SectionMonitorChainBuilder { + + return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 }) + } + /** Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections diff --git a/Sources/NSEntityDescription+DynamicModel.swift b/Sources/NSEntityDescription+DynamicModel.swift index f025c7b..f1a95d3 100644 --- a/Sources/NSEntityDescription+DynamicModel.swift +++ b/Sources/NSEntityDescription+DynamicModel.swift @@ -115,27 +115,6 @@ extension NSEntityDescription { } } - @nonobjc - internal var customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter] { - - get { - - if let userInfo = self.userInfo, - let value = userInfo[UserInfoKey.CoreStoreManagedObjectCustomGetterSetterByKeyPaths] { - - return value as! [KeyPathString: CoreStoreManagedObject.CustomGetterSetter] - } - return [:] - } - set { - - cs_setUserInfo { (userInfo) in - - userInfo[UserInfoKey.CoreStoreManagedObjectCustomGetterSetterByKeyPaths] = newValue - } - } - } - // MARK: Private @@ -151,7 +130,6 @@ extension NSEntityDescription { fileprivate static let CoreStoreManagedObjectUniqueConstraints = "CoreStoreManagedObjectUniqueConstraints" fileprivate static let CoreStoreManagedObjectKeyPathsByAffectedKeyPaths = "CoreStoreManagedObjectKeyPathsByAffectedKeyPaths" - fileprivate static let CoreStoreManagedObjectCustomGetterSetterByKeyPaths = "CoreStoreManagedObjectCustomGetterSetterByKeyPaths" }