From 801cf8d9f028e55258d960c373f04d6115e3e2ec Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 14 Jun 2017 17:37:46 +0900 Subject: [PATCH 01/11] Fixed ListMonitor bug for CoreStoreObjects where ListObservers don't get update notifications --- Sources/ListMonitor.swift | 4 +- Sources/Value.swift | 224 +++++++++++++++++++++++++++++++++----- 2 files changed, 198 insertions(+), 30 deletions(-) diff --git a/Sources/ListMonitor.swift b/Sources/ListMonitor.swift index 39536f4..f15b80d 100644 --- a/Sources/ListMonitor.swift +++ b/Sources/ListMonitor.swift @@ -704,13 +704,13 @@ public final class ListMonitor: Hashable { guard let `self` = self, let userInfo = note.userInfo, - let object = userInfo[String(describing: NSManagedObject.self)] as? ObjectType else { + let rawObject = userInfo[String(describing: NSManagedObject.self)] as? NSManagedObject else { return } callback( self, - object, + ObjectType.cs_fromRaw(object: rawObject), userInfo[String(describing: IndexPath.self)] as? IndexPath, userInfo["\(String(describing: IndexPath.self)).New"] as? IndexPath ) diff --git a/Sources/Value.swift b/Sources/Value.swift index 99ea32c..d1ef2c4 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -160,10 +160,9 @@ public enum ValueContainer { object, { () -> V in - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType)! } - ) + return V.cs_fromImportableNativeType( + object.rawObject!.value(forKey: self.keyPath)! as! V.ImportableNativeType + )! } ) } @@ -181,7 +180,7 @@ public enum ValueContainer { "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) CoreStore.assert( - self.isTransient || object.rawObject!.isEditableInContext() == true, + object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) let customSetter = (self.customSetter ?? { $1($2) }) @@ -190,9 +189,8 @@ public enum ValueContainer { { (newValue: V) -> Void in object.rawObject!.setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { $0.cs_toImportableNativeType() } + newValue.cs_toImportableNativeType(), + forKey: self.keyPath ) }, newValue @@ -201,6 +199,52 @@ public enum ValueContainer { } } + /** + The primitive value. Compared to `value`, `primitiveValue` bypasses all notification mechanisms. This is typically only used for setting values for transient properties. + */ + public var primitiveValue: V { + + get { + + CoreStore.assert( + self.parentObject != nil, + "Attempted to access primitive 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 primitive value outside it's designated queue." + ) + return V.cs_fromImportableNativeType( + object.rawObject!.primitiveValue(forKey: self.keyPath)! as! V.ImportableNativeType + )! + } + } + set { + + CoreStore.assert( + self.parentObject != nil, + "Attempted to access primitive 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 primitive value outside it's designated queue." + ) + CoreStore.assert( + self.isTransient || object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s primitive value from outside a transaction." + ) + object.rawObject!.setPrimitiveValue( + newValue.cs_toImportableNativeType(), + forKey: self.keyPath + ) + } + } + } + // MARK: AttributeProtocol @@ -363,10 +407,8 @@ public enum ValueContainer { object, { () -> V? in - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) } - ) + return (object.rawObject!.value(forKey: self.keyPath) as! V.ImportableNativeType?) + .flatMap(V.cs_fromImportableNativeType) } ) } @@ -384,7 +426,7 @@ public enum ValueContainer { "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) CoreStore.assert( - self.isTransient || object.rawObject!.isEditableInContext() == true, + object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) let customSetter = (self.customSetter ?? { $1($2) }) @@ -393,9 +435,8 @@ public enum ValueContainer { { (newValue: V?) -> Void in object.rawObject!.setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { $0?.cs_toImportableNativeType() } + newValue?.cs_toImportableNativeType(), + forKey: self.keyPath ) }, newValue @@ -404,6 +445,51 @@ public enum ValueContainer { } } + /** + The primitive value. Compared to `value`, `primitiveValue` bypasses all notification mechanisms. This is typically only used for setting values for transient properties. + */ + public var primitiveValue: V? { + + get { + + CoreStore.assert( + self.parentObject != nil, + "Attempted to access primitive 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 primitive value outside it's designated queue." + ) + return (object.rawObject!.primitiveValue(forKey: self.keyPath) as! V.ImportableNativeType?) + .flatMap(V.cs_fromImportableNativeType) + } + } + set { + + CoreStore.assert( + self.parentObject != nil, + "Attempted to access primitive 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 primitive value outside it's designated queue." + ) + CoreStore.assert( + self.isTransient || object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s primitive value from outside a transaction." + ) + object.rawObject!.setPrimitiveValue( + newValue?.cs_toImportableNativeType(), + forKey: self.keyPath + ) + } + } + } + // MARK: AttributeProtocol @@ -621,10 +707,7 @@ public enum TransformableContainer { object, { () -> V in - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { $0 as! V } - ) + return object.rawObject!.value(forKey: self.keyPath)! as! V } ) } @@ -642,7 +725,7 @@ public enum TransformableContainer { "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) CoreStore.assert( - self.isTransient || object.rawObject!.isEditableInContext() == true, + object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) let customSetter = (self.customSetter ?? { $1($2) }) @@ -652,7 +735,7 @@ public enum TransformableContainer { object.rawObject!.setValue( newValue, - forKvcKey: self.keyPath + forKey: self.keyPath ) }, newValue @@ -661,6 +744,50 @@ public enum TransformableContainer { } } + /** + The primitive value. Compared to `value`, `primitiveValue` bypasses all notification mechanisms. This is typically only used for setting values for transient properties. + */ + public var primitiveValue: V { + + get { + + CoreStore.assert( + self.parentObject != nil, + "Attempted to access primitive 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 primitive value outside it's designated queue." + ) + return object.rawObject!.primitiveValue(forKey: self.keyPath)! as! V + } + } + set { + + CoreStore.assert( + self.parentObject != nil, + "Attempted to access primitive 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 primitive value outside it's designated queue." + ) + CoreStore.assert( + self.isTransient || object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s primitive value from outside a transaction." + ) + object.rawObject!.setPrimitiveValue( + newValue, + forKey: self.keyPath + ) + } + } + } + // MARK: AttributeProtocol @@ -807,10 +934,7 @@ public enum TransformableContainer { object, { () -> V? in - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { $0 as! V? } - ) + object.rawObject!.value(forKey: self.keyPath) as! V? } ) } @@ -828,7 +952,7 @@ public enum TransformableContainer { "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) CoreStore.assert( - self.isTransient || object.rawObject!.isEditableInContext() == true, + object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) let customSetter = (self.customSetter ?? { $1($2) }) @@ -838,7 +962,7 @@ public enum TransformableContainer { object.rawObject!.setValue( newValue, - forKvcKey: self.keyPath + forKey: self.keyPath ) }, newValue @@ -847,6 +971,50 @@ public enum TransformableContainer { } } + /** + The primitive value. Compared to `value`, `primitiveValue` bypasses all notification mechanisms. This is typically only used for setting values for transient properties. + */ + public var primitiveValue: V? { + + get { + + CoreStore.assert( + self.parentObject != nil, + "Attempted to access primitive 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 primitive value outside it's designated queue." + ) + return object.rawObject!.primitiveValue(forKey: self.keyPath) as! V? + } + } + set { + + CoreStore.assert( + self.parentObject != nil, + "Attempted to access primitive 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 primitive value outside it's designated queue." + ) + CoreStore.assert( + self.isTransient || object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s primitive value from outside a transaction." + ) + object.rawObject!.setPrimitiveValue( + newValue, + forKey: self.keyPath + ) + } + } + } + // MARK: AttributeProtocol From e26573c18ee5d478ea35c841196f820ea7552a7c Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 14 Jun 2017 17:39:57 +0900 Subject: [PATCH 02/11] converted the Demo app's observer demo to use CoreStoreObject instead of NSManagedObject --- .../CoreStoreDemo.xcdatamodel/contents | 23 ++---- .../ListObserverDemoViewController.swift | 67 +++++++++------- .../ObjectObserverDemoViewController.swift | 44 +++++------ .../Palette.swift | 77 ++++++++++--------- 4 files changed, 107 insertions(+), 104 deletions(-) diff --git a/CoreStoreDemo/CoreStoreDemo/CoreStoreDemo.xcdatamodeld/CoreStoreDemo.xcdatamodel/contents b/CoreStoreDemo/CoreStoreDemo/CoreStoreDemo.xcdatamodeld/CoreStoreDemo.xcdatamodel/contents index 40c216e..7df0666 100644 --- a/CoreStoreDemo/CoreStoreDemo/CoreStoreDemo.xcdatamodeld/CoreStoreDemo.xcdatamodel/contents +++ b/CoreStoreDemo/CoreStoreDemo/CoreStoreDemo.xcdatamodeld/CoreStoreDemo.xcdatamodel/contents @@ -1,36 +1,25 @@ - - - - - - - - + - - + + - - + + - + - - - - diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift index 27f5fab..de745c9 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift @@ -10,7 +10,7 @@ import UIKit import CoreStore -private struct Static { +struct ColorsDemo { enum Filter: String { @@ -33,8 +33,8 @@ private struct Static { switch self { case .all: return Where(true) - case .light: return Where("%K >= %@", #keyPath(Palette.brightness), 0.9) - case .dark: return Where("%K <= %@", #keyPath(Palette.brightness), 0.4) + case .light: return Palette.where({ $0.brightness >= 0.9 }) + case .dark: return Palette.where({ $0.brightness <= 0.4 }) } } } @@ -45,25 +45,38 @@ private struct Static { self.palettes.refetch( self.filter.whereClause(), - OrderBy(.ascending(#keyPath(Palette.hue))) + Palette.orderBy(ascending: { $0.hue }) ) } } + static let stack: DataStack = { + + return DataStack( + CoreStoreSchema( + modelVersion: "ColorsDemo", + entities: [ + Entity("Palette"), + ], + versionLock: [ + "Palette": [0x8c25aa53c7c90a28, 0xa243a34d25f1a3a7, 0x56565b6935b6055a, 0x4f988bb257bf274f] + ] + ) + ) + }() + static let palettes: ListMonitor = { - try! CoreStore.addStorageAndWait( + try! ColorsDemo.stack.addStorageAndWait( SQLiteStore( fileName: "ColorsDemo.sqlite", - configuration: "ObservingDemo", localStorageOptions: .recreateStoreOnModelMismatch ) ) - - return CoreStore.monitorSectionedList( + return ColorsDemo.stack.monitorSectionedList( From(), - SectionBy(#keyPath(Palette.colorName)), - OrderBy(.ascending(#keyPath(Palette.hue))) + SectionBy(Palette.keyPath({ $0.colorName })), + Palette.orderBy(ascending: { $0.hue }) ) }() } @@ -77,7 +90,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver deinit { - Static.palettes.removeObserver(self) + ColorsDemo.palettes.removeObserver(self) } @@ -98,7 +111,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver ] let filterBarButton = UIBarButtonItem( - title: Static.filter.rawValue, + title: ColorsDemo.filter.rawValue, style: .plain, target: self, action: #selector(self.filterBarButtonItemTouched(_:)) @@ -113,9 +126,9 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver ] self.filterBarButton = filterBarButton - Static.palettes.addObserver(self) + ColorsDemo.palettes.addObserver(self) - self.setTable(enabled: !Static.palettes.isPendingRefetch) + self.setTable(enabled: !ColorsDemo.palettes.isPendingRefetch) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { @@ -137,19 +150,19 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver override func numberOfSections(in tableView: UITableView) -> Int { - return Static.palettes.numberOfSections() + return ColorsDemo.palettes.numberOfSections() } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return Static.palettes.numberOfObjectsInSection(section) + return ColorsDemo.palettes.numberOfObjectsInSection(section) } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell - let palette = Static.palettes[indexPath] + let palette = ColorsDemo.palettes[indexPath] cell.colorView?.backgroundColor = palette.color cell.label?.text = palette.colorText @@ -165,7 +178,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver self.performSegue( withIdentifier: "ObjectObserverDemoViewController", - sender: Static.palettes[indexPath] + sender: ColorsDemo.palettes[indexPath] ) } @@ -174,8 +187,8 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver switch editingStyle { case .delete: - let palette = Static.palettes[indexPath] - CoreStore.perform( + let palette = ColorsDemo.palettes[indexPath] + ColorsDemo.stack.perform( asynchronous: { (transaction) in transaction.delete(palette) @@ -190,7 +203,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return Static.palettes.sectionInfoAtIndex(section).name + return ColorsDemo.palettes.sectionInfoAtIndex(section).name } @@ -213,7 +226,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver func listMonitorDidRefetch(_ monitor: ListMonitor) { - self.filterBarButton?.title = Static.filter.rawValue + self.filterBarButton?.title = ColorsDemo.filter.rawValue self.tableView.reloadData() self.setTable(enabled: true) } @@ -235,7 +248,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver if let cell = self.tableView.cellForRow(at: indexPath) as? PaletteTableViewCell { - let palette = Static.palettes[indexPath] + let palette = ColorsDemo.palettes[indexPath] cell.colorView?.backgroundColor = palette.color cell.label?.text = palette.colorText } @@ -268,7 +281,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver @IBAction private dynamic func resetBarButtonItemTouched(_ sender: AnyObject?) { - CoreStore.perform( + ColorsDemo.stack.perform( asynchronous: { (transaction) in transaction.deleteAll(From()) @@ -279,16 +292,16 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver @IBAction private dynamic func filterBarButtonItemTouched(_ sender: AnyObject?) { - Static.filter = Static.filter.next() + ColorsDemo.filter = ColorsDemo.filter.next() } @IBAction private dynamic func addBarButtonItemTouched(_ sender: AnyObject?) { - CoreStore.perform( + ColorsDemo.stack.perform( asynchronous: { (transaction) in let palette = transaction.create(Into()) - palette.setInitialValues() + palette.setInitialValues(in: transaction) }, 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 ce95fe4..d50ca20 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift @@ -29,7 +29,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { if let palette = newValue { - self.monitor = CoreStore.monitorObject(palette) + self.monitor = ColorsDemo.stack.monitorObject(palette) } else { @@ -50,22 +50,22 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { required init?(coder aDecoder: NSCoder) { - if let palette = CoreStore.fetchOne(From(), OrderBy(.ascending(#keyPath(Palette.hue)))) { + if let palette = ColorsDemo.stack.fetchOne(From(), Palette.orderBy(ascending: { $0.hue })) { - self.monitor = CoreStore.monitorObject(palette) + self.monitor = ColorsDemo.stack.monitorObject(palette) } else { - _ = try? CoreStore.perform( + _ = try? ColorsDemo.stack.perform( synchronous: { (transaction) in - let palette = transaction.create(Into(Palette.self)) - palette.setInitialValues() + let palette = transaction.create(Into()) + palette.setInitialValues(in: transaction) } ) - let palette = CoreStore.fetchOne(From(), OrderBy(.ascending(#keyPath(Palette.hue))))! - self.monitor = CoreStore.monitorObject(palette) + let palette = ColorsDemo.stack.fetchOne(From(), Palette.orderBy(ascending: { $0.hue }))! + self.monitor = ColorsDemo.stack.monitorObject(palette) } super.init(coder: aDecoder) @@ -121,12 +121,12 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { @IBAction dynamic func hueSliderValueDidChange(_ sender: AnyObject?) { let hue = self.hueSlider?.value ?? 0 - CoreStore.perform( + ColorsDemo.stack.perform( asynchronous: { [weak self] (transaction) in if let palette = transaction.edit(self?.monitor?.object) { - palette.hue = Int32(hue) + palette.hue .= Int(hue) } }, completion: { _ in } @@ -136,12 +136,12 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { @IBAction dynamic func saturationSliderValueDidChange(_ sender: AnyObject?) { let saturation = self.saturationSlider?.value ?? 0 - CoreStore.perform( + ColorsDemo.stack.perform( asynchronous: { [weak self] (transaction) in if let palette = transaction.edit(self?.monitor?.object) { - palette.saturation = saturation + palette.saturation .= saturation } }, completion: { _ in } @@ -151,12 +151,12 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { @IBAction dynamic func brightnessSliderValueDidChange(_ sender: AnyObject?) { let brightness = self.brightnessSlider?.value ?? 0 - CoreStore.perform( + ColorsDemo.stack.perform( asynchronous: { [weak self] (transaction) in if let palette = transaction.edit(self?.monitor?.object) { - palette.brightness = brightness + palette.brightness .= brightness } }, completion: { _ in } @@ -165,7 +165,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { @IBAction dynamic func deleteBarButtonTapped(_ sender: AnyObject?) { - CoreStore.perform( + ColorsDemo.stack.perform( asynchronous: { [weak self] (transaction) in transaction.delete(self?.monitor?.object) @@ -176,7 +176,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { func reloadPaletteInfo(_ palette: Palette, changedKeys: Set?) { - self.colorNameLabel?.text = palette.colorName + self.colorNameLabel?.text = palette.colorName.value let color = palette.color self.colorNameLabel?.textColor = color @@ -184,17 +184,17 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { self.hsbLabel?.text = palette.colorText - if changedKeys == nil || changedKeys?.contains(#keyPath(Palette.hue)) == true { + if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.hue }) == true { - self.hueSlider?.value = Float(palette.hue) + self.hueSlider?.value = Float(palette.hue.value) } - if changedKeys == nil || changedKeys?.contains(#keyPath(Palette.saturation)) == true { + if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.saturation }) == true { - self.saturationSlider?.value = palette.saturation + self.saturationSlider?.value = palette.saturation.value } - if changedKeys == nil || changedKeys?.contains(#keyPath(Palette.brightness)) == true { + if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.brightness }) == true { - self.brightnessSlider?.value = palette.brightness + self.brightnessSlider?.value = palette.brightness.value } } } diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift index fffc643..07fd502 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift @@ -14,64 +14,65 @@ import CoreStore // MARK: - Palette -class Palette: NSManagedObject { - - @NSManaged var hue: Int32 - @NSManaged var saturation: Float - @NSManaged var brightness: Float +final class Palette: CoreStoreObject { - @objc dynamic var colorName: String { + let hue = Value.Required("hue") + let saturation = Value.Required("saturation") + let brightness = Value.Required("brightness") + + let colorName = Value.Optional( + "colorName", + isTransient: true, + customGetter: Palette.getCachedColorName + ) + + private static func getCachedColorName(_ instance: Palette, _ getValue: () -> String?) -> String? { - get { + if let colorName = getValue() { - let KVCKey = #keyPath(Palette.colorName) - if let colorName = self.getValue(forKvcKey: KVCKey) as? String { - - return colorName - } - - let colorName: String - switch self.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" - } - - self.setPrimitiveValue(colorName, forKey: KVCKey) return colorName } - set { + + let colorName: String + switch instance.hue.value % 360 { - self.setValue(newValue.cs_toImportableNativeType(), forKvcKey: #keyPath(Palette.colorName)) + 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" } + + instance.colorName.primitiveValue = colorName + return colorName } +} + +extension Palette { var color: UIColor { return UIColor( - hue: CGFloat(self.hue) / 360.0, - saturation: CGFloat(self.saturation), - brightness: CGFloat(self.brightness), + hue: CGFloat(self.hue.value) / 360.0, + saturation: CGFloat(self.saturation.value), + brightness: CGFloat(self.brightness.value), alpha: 1.0 ) } var colorText: String { - return "H: \(self.hue)˚, S: \(round(self.saturation * 100.0))%, B: \(round(self.brightness * 100.0))%" + return "H: \(self.hue.value)˚, S: \(round(self.saturation.value * 100.0))%, B: \(round(self.brightness.value * 100.0))%" } - func setInitialValues() { + func setInitialValues(in transaction: BaseDataTransaction) { - self.hue = Int32(arc4random_uniform(360)) - self.saturation = 1.0 - self.brightness = Float(arc4random_uniform(70) + 30) / 100.0 + self.hue .= Int(arc4random_uniform(360)) + self.saturation .= Float(1.0) + self.brightness .= Float(arc4random_uniform(70) + 30) / 100.0 } } From 6fcdf3d011e5e6475419863c607c93194217cdb5 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 14 Jun 2017 17:43:34 +0900 Subject: [PATCH 03/11] fix logger demo --- .../CoreStoreDemo/Loggers Demo/CustomLoggerViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CoreStoreDemo/CoreStoreDemo/Loggers Demo/CustomLoggerViewController.swift b/CoreStoreDemo/CoreStoreDemo/Loggers Demo/CustomLoggerViewController.swift index ff02f89..5bf30fd 100644 --- a/CoreStoreDemo/CoreStoreDemo/Loggers Demo/CustomLoggerViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/Loggers Demo/CustomLoggerViewController.swift @@ -113,7 +113,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger { case 2?: DispatchQueue.global(qos: .background).async { - _ = self.dataStack.fetchOne(From()) + _ = self.dataStack.fetchOne(From()) } default: From 5689158b438da341f9e8919486bd74f64c23a012 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 14 Jun 2017 21:50:58 +0900 Subject: [PATCH 04/11] version bump --- CoreStore.podspec | 2 +- Sources/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CoreStore.podspec b/CoreStore.podspec index cd228b3..e01a6fd 100644 --- a/CoreStore.podspec +++ b/CoreStore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreStore" - s.version = "4.0.4" + s.version = "4.0.5" s.license = "MIT" s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift" s.homepage = "https://github.com/JohnEstropia/CoreStore" diff --git a/Sources/Info.plist b/Sources/Info.plist index 32fbf9d..5d7e8c4 100644 --- a/Sources/Info.plist +++ b/Sources/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 4.0.4 + 4.0.5 CFBundleSignature ???? CFBundleVersion From 746d697691a1253db0ecb0b0df4fec4dcc233940 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Thu, 15 Jun 2017 08:27:08 +0900 Subject: [PATCH 05/11] WIP: new PartialObject to act as faster KVC wrappers when implementing custom getters and setters for CoreStoreObject --- CoreStore.xcodeproj/project.pbxproj | 10 ++ Sources/CoreStoreObject.swift | 20 ++++ Sources/DynamicObject.swift | 11 -- Sources/PartialObject.swift | 135 +++++++++++++++++++++++ Sources/Value.swift | 162 ++++++++++++---------------- 5 files changed, 233 insertions(+), 105 deletions(-) create mode 100644 Sources/PartialObject.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 0801a46..c34ff5f 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -242,6 +242,10 @@ B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; }; B53B27611EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; }; B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; }; + B53CA9A21EF1EF1600E0F440 /* PartialObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */; }; + B53CA9A31EF1EF1600E0F440 /* PartialObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */; }; + B53CA9A41EF1EF1600E0F440 /* PartialObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */; }; + B53CA9A51EF1EF1600E0F440 /* PartialObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */; }; B53FB9FE1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; B53FBA011CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; @@ -757,6 +761,7 @@ B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+CoreStore.swift"; sourceTree = ""; }; B538BA701D15B3E30003A766 /* CoreStoreBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreStoreBridge.m; sourceTree = ""; }; B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreManagedObject.swift; sourceTree = ""; }; + B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PartialObject.swift; sourceTree = ""; }; B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSMigrationResult.swift; sourceTree = ""; }; B53FBA031CAB300C00F0D40A /* CSMigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSMigrationType.swift; sourceTree = ""; }; B53FBA0A1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSCoreStore+Migrating.swift"; sourceTree = ""; }; @@ -1229,6 +1234,7 @@ isa = PBXGroup; children = ( B5D339D71E9489AB00C880DE /* CoreStoreObject.swift */, + B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */, B52F74391E9B8724005F3DAC /* Dynamic Schema */, B5D339DC1E9489C700C880DE /* DynamicObject.swift */, B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */, @@ -1826,6 +1832,7 @@ B5FAD6AE1B518DCB00714891 /* CoreStore+Migration.swift in Sources */, B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */, B546F9731C9C553300D5AC55 /* SetupResult.swift in Sources */, + B53CA9A21EF1EF1600E0F440 /* PartialObject.swift in Sources */, B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */, B5E84F111AFF847B0064E85B /* Select.swift in Sources */, B51260931E9B28F100402229 /* EntityIdentifier.swift in Sources */, @@ -2011,6 +2018,7 @@ 82BA18A31C4BBD2200A0916E /* DataStack.swift in Sources */, 82BA18C81C4BBD5900A0916E /* MigrationChain.swift in Sources */, B546F9741C9C553300D5AC55 /* SetupResult.swift in Sources */, + B53CA9A31EF1EF1600E0F440 /* PartialObject.swift in Sources */, 82BA18B11C4BBD3100A0916E /* SaveResult.swift in Sources */, 82BA18DD1C4BBE1400A0916E /* NSFetchedResultsController+Convenience.swift in Sources */, B51260941E9B28F100402229 /* EntityIdentifier.swift in Sources */, @@ -2196,6 +2204,7 @@ B52DD1961BE1F92500949AFE /* DataStack.swift in Sources */, B5ECDBFD1CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */, B52DD1BD1BE1F94300949AFE /* NSManagedObject+Convenience.swift in Sources */, + B53CA9A51EF1EF1600E0F440 /* PartialObject.swift in Sources */, B52DD1AD1BE1F93900949AFE /* Where.swift in Sources */, B53FBA1C1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */, B51260961E9B28F100402229 /* EntityIdentifier.swift in Sources */, @@ -2381,6 +2390,7 @@ B56321A81BD65219006C9394 /* NSManagedObject+Convenience.swift in Sources */, B546F9751C9C553300D5AC55 /* SetupResult.swift in Sources */, B56321981BD65216006C9394 /* Where.swift in Sources */, + B53CA9A41EF1EF1600E0F440 /* PartialObject.swift in Sources */, B5202CFD1C046E8400DED140 /* NSFetchedResultsController+Convenience.swift in Sources */, B5FE4DA91C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, B51260951E9B28F100402229 /* EntityIdentifier.swift in Sources */, diff --git a/Sources/CoreStoreObject.swift b/Sources/CoreStoreObject.swift index 910644f..2ec6c4c 100644 --- a/Sources/CoreStoreObject.swift +++ b/Sources/CoreStoreObject.swift @@ -135,3 +135,23 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable { } } } + + +// MARK: - DynamicObject where Self: CoreStoreObject + +public extension DynamicObject where Self: CoreStoreObject { + + public func partialObject() -> PartialObject { + + CoreStore.assert( + !self.isMeta, + "Attempted to create a \(cs_typeName(PartialObject.self)) from a meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return PartialObject(self.rawObject!) + } + + internal static var meta: Self { + + return self.init(asMeta: ()) + } +} diff --git a/Sources/DynamicObject.swift b/Sources/DynamicObject.swift index db733a3..c48bdba 100644 --- a/Sources/DynamicObject.swift +++ b/Sources/DynamicObject.swift @@ -155,14 +155,3 @@ extension CoreStoreObject { return self.rawObject! } } - - -// MARK: - Internal - -internal extension DynamicObject where Self: CoreStoreObject { - - internal static var meta: Self { - - return self.init(asMeta: ()) - } -} diff --git a/Sources/PartialObject.swift b/Sources/PartialObject.swift new file mode 100644 index 0000000..a47cebf --- /dev/null +++ b/Sources/PartialObject.swift @@ -0,0 +1,135 @@ +// +// PartialObject.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: - PartialObject + +public struct PartialObject { + + public func completeObject() -> O { + + return O.cs_fromRaw(object: self.rawObject) + } + + public func value(for property: (O) -> ValueContainer.Required) -> V { + + return V.cs_fromImportableNativeType( + self.rawObject.value(forKey: property(O.meta).keyPath)! as! V.ImportableNativeType + )! + } + + public func value(for property: (O) -> ValueContainer.Optional) -> V? { + + return (self.rawObject.value(forKey: property(O.meta).keyPath) as! V.ImportableNativeType?) + .flatMap(V.cs_fromImportableNativeType) + } + + public func value(for property: (O) -> TransformableContainer.Required) -> V { + + return self.rawObject.value(forKey: property(O.meta).keyPath)! as! V + } + + public func value(for property: (O) -> TransformableContainer.Optional) -> V? { + + return self.rawObject.value(forKey: property(O.meta).keyPath) as! V? + } + + public func setValue(_ value: V, for property: (O) -> ValueContainer.Required) { + + self.rawObject.setValue( + value.cs_toImportableNativeType(), + forKey: property(O.meta).keyPath + ) + } + + public func setValue(_ value: V?, for property: (O) -> ValueContainer.Optional) { + + self.rawObject.setValue( + value?.cs_toImportableNativeType(), + forKey: property(O.meta).keyPath + ) + } + + public func setValue(_ value: V, for property: (O) -> TransformableContainer.Required) { + + self.rawObject.setValue( + value, + forKey: property(O.meta).keyPath + ) + } + + public func setValue(_ value: V?, for property: (O) -> TransformableContainer.Optional) { + + self.rawObject.setValue( + value, + forKey: property(O.meta).keyPath + ) + } + + public func setPrimitiveValue(_ value: V, for property: (O) -> ValueContainer.Required) { + + self.rawObject.setPrimitiveValue( + value.cs_toImportableNativeType(), + forKey: property(O.meta).keyPath + ) + } + + public func setPrimitiveValue(_ value: V?, for property: (O) -> ValueContainer.Optional) { + + self.rawObject.setPrimitiveValue( + value?.cs_toImportableNativeType(), + forKey: property(O.meta).keyPath + ) + } + + public func setPrimitiveValue(_ value: V, for property: (O) -> TransformableContainer.Required) { + + self.rawObject.setPrimitiveValue( + value, + forKey: property(O.meta).keyPath + ) + } + + public func setPrimitiveValue(_ value: V?, for property: (O) -> TransformableContainer.Optional) { + + self.rawObject.setPrimitiveValue( + value, + forKey: property(O.meta).keyPath + ) + } + + + // MARK: Internal + + internal let rawObject: NSManagedObject + + internal init(_ rawObject: NSManagedObject) { + + self.rawObject = rawObject + } +} diff --git a/Sources/Value.swift b/Sources/Value.swift index d1ef2c4..e8f1575 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -123,8 +123,8 @@ public enum ValueContainer { isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: ((_ `self`: O, _ getValue: () -> V) -> V)? = nil, - customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void)? = nil, + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath @@ -155,16 +155,13 @@ public enum ValueContainer { object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - let customGetter = (self.customGetter ?? { $1() }) - return customGetter( - object, - { () -> V in - - return V.cs_fromImportableNativeType( - object.rawObject!.value(forKey: self.keyPath)! as! V.ImportableNativeType - )! - } - ) + if let customGetter = self.customGetter { + + return customGetter(PartialObject(object.rawObject!)) + } + return V.cs_fromImportableNativeType( + object.rawObject!.value(forKey: self.keyPath)! as! V.ImportableNativeType + )! } } set { @@ -183,17 +180,13 @@ public enum ValueContainer { object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) - let customSetter = (self.customSetter ?? { $1($2) }) - customSetter( - object, - { (newValue: V) -> Void in - - object.rawObject!.setValue( - newValue.cs_toImportableNativeType(), - forKey: self.keyPath - ) - }, - newValue + if let customSetter = self.customSetter { + + return customSetter(PartialObject(object.rawObject!), newValue) + } + return object.rawObject!.setValue( + newValue.cs_toImportableNativeType(), + forKey: self.keyPath ) } } @@ -274,15 +267,12 @@ public enum ValueContainer { return { (_ id: Any) -> Any? in let rawObject = id as! CoreStoreManagedObject - let value = customGetter( - O.cs_fromRaw(object: rawObject), - { - rawObject.getValue( - forKvcKey: keyPath, - didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType!)! } - ) - } - ) + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + let value = customGetter(PartialObject(rawObject)) return value.cs_toImportableNativeType() } } @@ -297,16 +287,13 @@ public enum ValueContainer { return { (_ id: Any, _ newValue: Any?) -> Void in let rawObject = id as! CoreStoreManagedObject + rawObject.willChangeValue(forKey: keyPath) + defer { + + rawObject.didChangeValue(forKey: keyPath) + } customSetter( - O.cs_fromRaw(object: rawObject), - { (userValue: V) -> Void in - - rawObject.setValue( - userValue, - forKvcKey: keyPath, - willSetValue: { $0.cs_toImportableNativeType() } - ) - }, + PartialObject(rawObject), V.cs_fromImportableNativeType(newValue as! V.ImportableNativeType)! ) } @@ -315,8 +302,8 @@ public enum ValueContainer { // MARK: Private - private let customGetter: ((_ `self`: O, _ getValue: () -> V) -> V)? - private let customSetter: ((_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void)? + private let customGetter: ((_ partialObject: PartialObject) -> V)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? } @@ -370,8 +357,8 @@ public enum ValueContainer { isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: ((_ `self`: O, _ getValue: () -> V?) -> V?)? = nil, - customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void)? = nil, + customGetter: ((_ partialObject: PartialObject) -> V?)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath @@ -402,15 +389,12 @@ public enum ValueContainer { object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - let customGetter = (self.customGetter ?? { $1() }) - return customGetter( - object, - { () -> V? in - - return (object.rawObject!.value(forKey: self.keyPath) as! V.ImportableNativeType?) - .flatMap(V.cs_fromImportableNativeType) - } - ) + if let customGetter = self.customGetter { + + return customGetter(PartialObject(object.rawObject!)) + } + return (object.rawObject!.value(forKey: self.keyPath) as! V.ImportableNativeType?) + .flatMap(V.cs_fromImportableNativeType) } } set { @@ -429,17 +413,13 @@ public enum ValueContainer { object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) - let customSetter = (self.customSetter ?? { $1($2) }) - customSetter( - object, - { (newValue: V?) -> Void in - - object.rawObject!.setValue( - newValue?.cs_toImportableNativeType(), - forKey: self.keyPath - ) - }, - newValue + if let customSetter = self.customSetter { + + return customSetter(PartialObject(object.rawObject!), newValue) + } + object.rawObject!.setValue( + newValue?.cs_toImportableNativeType(), + forKey: self.keyPath ) } } @@ -518,15 +498,12 @@ public enum ValueContainer { return { (_ id: Any) -> Any? in let rawObject = id as! CoreStoreManagedObject - let value = customGetter( - O.cs_fromRaw(object: rawObject), - { - rawObject.getValue( - forKvcKey: keyPath, - didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) } - ) - } - ) + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + let value = customGetter(PartialObject(rawObject)) return value?.cs_toImportableNativeType() } } @@ -541,16 +518,13 @@ public enum ValueContainer { return { (_ id: Any, _ newValue: Any?) -> Void in let rawObject = id as! CoreStoreManagedObject + rawObject.willChangeValue(forKey: keyPath) + defer { + + rawObject.didChangeValue(forKey: keyPath) + } customSetter( - O.cs_fromRaw(object: rawObject), - { (userValue: V?) -> Void in - - rawObject.setValue( - userValue, - forKvcKey: keyPath, - willSetValue: { $0?.cs_toImportableNativeType() } - ) - }, + PartialObject(rawObject), (newValue as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) ) } @@ -559,8 +533,8 @@ public enum ValueContainer { // MARK: Private - private let customGetter: ((_ `self`: O, _ getValue: () -> V?) -> V?)? - private let customSetter: ((_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void)? + private let customGetter: ((_ partialObject: PartialObject) -> V?)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? } } @@ -593,7 +567,7 @@ public extension ValueContainer.Required where V: EmptyableAttributeType { isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: ((_ `self`: O, _ getValue: () -> V) -> V)? = nil, + customGetter: ((_ `self`: PartialObject, _ getValue: () -> V) -> V)? = nil, customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { @@ -670,7 +644,7 @@ public enum TransformableContainer { isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: ((_ `self`: O, _ getValue: () -> V) -> V)? = nil, + customGetter: ((_ `self`: PartialObject, _ getValue: () -> V) -> V)? = nil, customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { @@ -704,7 +678,7 @@ public enum TransformableContainer { ) let customGetter = (self.customGetter ?? { $1() }) return customGetter( - object, + PartialObject(object.rawObject!), { () -> V in return object.rawObject!.value(forKey: self.keyPath)! as! V @@ -818,7 +792,7 @@ public enum TransformableContainer { let rawObject = id as! CoreStoreManagedObject return customGetter( - O.cs_fromRaw(object: rawObject), + PartialObject(rawObject), { rawObject.getValue(forKvcKey: keyPath) as! V } ) } @@ -848,7 +822,7 @@ public enum TransformableContainer { // MARK: Private - private let customGetter: ((_ `self`: O, _ getValue: () -> V) -> V)? + private let customGetter: ((_ `self`: PartialObject, _ getValue: () -> V) -> V)? private let customSetter: ((_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void)? } @@ -897,7 +871,7 @@ public enum TransformableContainer { isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: ((_ `self`: O, _ getValue: () -> V?) -> V?)? = nil, + customGetter: ((_ `self`: PartialObject, _ getValue: () -> V?) -> V?)? = nil, customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { @@ -931,7 +905,7 @@ public enum TransformableContainer { ) let customGetter = (self.customGetter ?? { $1() }) return customGetter( - object, + PartialObject(object.rawObject!), { () -> V? in object.rawObject!.value(forKey: self.keyPath) as! V? @@ -1045,7 +1019,7 @@ public enum TransformableContainer { let rawObject = id as! CoreStoreManagedObject return customGetter( - O.cs_fromRaw(object: rawObject), + PartialObject(rawObject), { rawObject.getValue(forKvcKey: keyPath) as! V? } ) } @@ -1083,7 +1057,7 @@ public enum TransformableContainer { // MARK: Private - private let customGetter: ((_ `self`: O, _ getValue: () -> V?) -> V?)? + private let customGetter: ((_ `self`: PartialObject, _ getValue: () -> V?) -> V?)? private let customSetter: ((_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void)? } } From 2c394965b850df42a195812bcf74e80b8fe4f26e Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Fri, 16 Jun 2017 01:02:23 +0900 Subject: [PATCH 06/11] remove direct access to CoreStoreObject.primitiveValues and replace PartialObject --- Sources/Value.swift | 309 +++++++++----------------------------------- 1 file changed, 58 insertions(+), 251 deletions(-) diff --git a/Sources/Value.swift b/Sources/Value.swift index e8f1575..a16e160 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -192,52 +192,6 @@ public enum ValueContainer { } } - /** - The primitive value. Compared to `value`, `primitiveValue` bypasses all notification mechanisms. This is typically only used for setting values for transient properties. - */ - public var primitiveValue: V { - - get { - - CoreStore.assert( - self.parentObject != nil, - "Attempted to access primitive 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 primitive value outside it's designated queue." - ) - return V.cs_fromImportableNativeType( - object.rawObject!.primitiveValue(forKey: self.keyPath)! as! V.ImportableNativeType - )! - } - } - set { - - CoreStore.assert( - self.parentObject != nil, - "Attempted to access primitive 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 primitive value outside it's designated queue." - ) - CoreStore.assert( - self.isTransient || object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s primitive value from outside a transaction." - ) - object.rawObject!.setPrimitiveValue( - newValue.cs_toImportableNativeType(), - forKey: self.keyPath - ) - } - } - } - // MARK: AttributeProtocol @@ -425,51 +379,6 @@ public enum ValueContainer { } } - /** - The primitive value. Compared to `value`, `primitiveValue` bypasses all notification mechanisms. This is typically only used for setting values for transient properties. - */ - public var primitiveValue: V? { - - get { - - CoreStore.assert( - self.parentObject != nil, - "Attempted to access primitive 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 primitive value outside it's designated queue." - ) - return (object.rawObject!.primitiveValue(forKey: self.keyPath) as! V.ImportableNativeType?) - .flatMap(V.cs_fromImportableNativeType) - } - } - set { - - CoreStore.assert( - self.parentObject != nil, - "Attempted to access primitive 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 primitive value outside it's designated queue." - ) - CoreStore.assert( - self.isTransient || object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s primitive value from outside a transaction." - ) - object.rawObject!.setPrimitiveValue( - newValue?.cs_toImportableNativeType(), - forKey: self.keyPath - ) - } - } - } - // MARK: AttributeProtocol @@ -567,8 +476,8 @@ public extension ValueContainer.Required where V: EmptyableAttributeType { isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: ((_ `self`: PartialObject, _ getValue: () -> V) -> V)? = nil, - customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void)? = nil, + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.init( @@ -644,8 +553,8 @@ public enum TransformableContainer { isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: ((_ `self`: PartialObject, _ getValue: () -> V) -> V)? = nil, - customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void)? = nil, + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath @@ -676,14 +585,11 @@ public enum TransformableContainer { object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - let customGetter = (self.customGetter ?? { $1() }) - return customGetter( - PartialObject(object.rawObject!), - { () -> V in - - return object.rawObject!.value(forKey: self.keyPath)! as! V - } - ) + if let customGetter = self.customGetter { + + return customGetter(PartialObject(object.rawObject!)) + } + return object.rawObject!.value(forKey: self.keyPath)! as! V } } set { @@ -702,59 +608,11 @@ public enum TransformableContainer { object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) - let customSetter = (self.customSetter ?? { $1($2) }) - customSetter( - object, - { (newValue: V) -> Void in - - object.rawObject!.setValue( - newValue, - forKey: self.keyPath - ) - }, - newValue - ) - } - } - } - - /** - The primitive value. Compared to `value`, `primitiveValue` bypasses all notification mechanisms. This is typically only used for setting values for transient properties. - */ - public var primitiveValue: V { - - get { - - CoreStore.assert( - self.parentObject != nil, - "Attempted to access primitive 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 primitive value outside it's designated queue." - ) - return object.rawObject!.primitiveValue(forKey: self.keyPath)! as! V - } - } - set { - - CoreStore.assert( - self.parentObject != nil, - "Attempted to access primitive 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 primitive value outside it's designated queue." - ) - CoreStore.assert( - self.isTransient || object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s primitive value from outside a transaction." - ) - object.rawObject!.setPrimitiveValue( + if let customSetter = self.customSetter { + + return customSetter(PartialObject(object.rawObject!), newValue) + } + object.rawObject!.setValue( newValue, forKey: self.keyPath ) @@ -791,10 +649,13 @@ public enum TransformableContainer { return { (_ id: Any) -> Any? in let rawObject = id as! CoreStoreManagedObject - return customGetter( - PartialObject(rawObject), - { rawObject.getValue(forKvcKey: keyPath) as! V } - ) + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + let value = customGetter(PartialObject(rawObject)) + return value } } @@ -808,12 +669,13 @@ public enum TransformableContainer { return { (_ id: Any, _ newValue: Any?) -> Void in let rawObject = id as! CoreStoreManagedObject + rawObject.willChangeValue(forKey: keyPath) + defer { + + rawObject.didChangeValue(forKey: keyPath) + } customSetter( - O.cs_fromRaw(object: rawObject), - { (userValue: V) -> Void in - - rawObject.setValue(userValue, forKvcKey: keyPath) - }, + PartialObject(rawObject), newValue as! V ) } @@ -822,8 +684,8 @@ public enum TransformableContainer { // MARK: Private - private let customGetter: ((_ `self`: PartialObject, _ getValue: () -> V) -> V)? - private let customSetter: ((_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void)? + private let customGetter: ((_ partialObject: PartialObject) -> V)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? } @@ -871,8 +733,8 @@ public enum TransformableContainer { isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: ((_ `self`: PartialObject, _ getValue: () -> V?) -> V?)? = nil, - customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void)? = nil, + customGetter: ((_ partialObject: PartialObject) -> V?)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath @@ -903,14 +765,11 @@ public enum TransformableContainer { object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - let customGetter = (self.customGetter ?? { $1() }) - return customGetter( - PartialObject(object.rawObject!), - { () -> V? in - - object.rawObject!.value(forKey: self.keyPath) as! V? - } - ) + if let customGetter = self.customGetter { + + return customGetter(PartialObject(object.rawObject!)) + } + return object.rawObject!.value(forKey: self.keyPath) as! V? } } set { @@ -929,59 +788,11 @@ public enum TransformableContainer { object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) - let customSetter = (self.customSetter ?? { $1($2) }) - customSetter( - object, - { (newValue: V?) -> Void in - - object.rawObject!.setValue( - newValue, - forKey: self.keyPath - ) - }, - newValue - ) - } - } - } - - /** - The primitive value. Compared to `value`, `primitiveValue` bypasses all notification mechanisms. This is typically only used for setting values for transient properties. - */ - public var primitiveValue: V? { - - get { - - CoreStore.assert( - self.parentObject != nil, - "Attempted to access primitive 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 primitive value outside it's designated queue." - ) - return object.rawObject!.primitiveValue(forKey: self.keyPath) as! V? - } - } - set { - - CoreStore.assert( - self.parentObject != nil, - "Attempted to access primitive 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 primitive value outside it's designated queue." - ) - CoreStore.assert( - self.isTransient || object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s primitive value from outside a transaction." - ) - object.rawObject!.setPrimitiveValue( + if let customSetter = self.customSetter { + + return customSetter(PartialObject(object.rawObject!), newValue) + } + object.rawObject!.setValue( newValue, forKey: self.keyPath ) @@ -1018,37 +829,33 @@ public enum TransformableContainer { return { (_ id: Any) -> Any? in let rawObject = id as! CoreStoreManagedObject - return customGetter( - PartialObject(rawObject), - { rawObject.getValue(forKvcKey: keyPath) as! V? } - ) + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + let value = customGetter(PartialObject(rawObject)) + return value } } internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in - let keyPath = self.keyPath guard let customSetter = self.customSetter else { - guard let _ = self.customGetter else { - - return nil - } - return { (_ id: Any, _ newValue: Any?) -> Void in - - let rawObject = id as! CoreStoreManagedObject - rawObject.setValue(newValue, forKvcKey: keyPath) - } + 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( - O.cs_fromRaw(object: rawObject), - { (userValue: V?) -> Void in - - rawObject.setValue(userValue, forKvcKey: keyPath) - }, + PartialObject(rawObject), newValue as! V? ) } @@ -1057,8 +864,8 @@ public enum TransformableContainer { // MARK: Private - private let customGetter: ((_ `self`: PartialObject, _ getValue: () -> V?) -> V?)? - private let customSetter: ((_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void)? + private let customGetter: ((_ partialObject: PartialObject) -> V?)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? } } From 195b60615b9b870148253e7eacaea71127e28ab4 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Fri, 16 Jun 2017 01:42:09 +0900 Subject: [PATCH 07/11] added missing utilities --- Sources/PartialObject.swift | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Sources/PartialObject.swift b/Sources/PartialObject.swift index a47cebf..3f3f90d 100644 --- a/Sources/PartialObject.swift +++ b/Sources/PartialObject.swift @@ -91,6 +91,29 @@ public struct PartialObject { ) } + public func primitiveValue(for property: (O) -> ValueContainer.Required) -> V { + + return V.cs_fromImportableNativeType( + self.rawObject.primitiveValue(forKey: property(O.meta).keyPath)! as! V.ImportableNativeType + )! + } + + public func primitiveValue(for property: (O) -> ValueContainer.Optional) -> V? { + + return (self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) as! V.ImportableNativeType?) + .flatMap(V.cs_fromImportableNativeType) + } + + public func primitiveValue(for property: (O) -> TransformableContainer.Required) -> V { + + return self.rawObject.primitiveValue(forKey: property(O.meta).keyPath)! as! V + } + + public func primitiveValue(for property: (O) -> TransformableContainer.Optional) -> V? { + + return self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) as! V? + } + public func setPrimitiveValue(_ value: V, for property: (O) -> ValueContainer.Required) { self.rawObject.setPrimitiveValue( From 1a99fea820eec3a429eeb91d8b536c39354dbbf2 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 17 Jun 2017 01:55:36 +0900 Subject: [PATCH 08/11] complete PartialObject utilities --- Sources/PartialObject.swift | 126 ++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 57 deletions(-) diff --git a/Sources/PartialObject.swift b/Sources/PartialObject.swift index 3f3f90d..b103d21 100644 --- a/Sources/PartialObject.swift +++ b/Sources/PartialObject.swift @@ -36,6 +36,9 @@ public struct PartialObject { return O.cs_fromRaw(object: self.rawObject) } + + // MARK: Value.Required utilities + public func value(for property: (O) -> ValueContainer.Required) -> V { return V.cs_fromImportableNativeType( @@ -43,22 +46,6 @@ public struct PartialObject { )! } - public func value(for property: (O) -> ValueContainer.Optional) -> V? { - - return (self.rawObject.value(forKey: property(O.meta).keyPath) as! V.ImportableNativeType?) - .flatMap(V.cs_fromImportableNativeType) - } - - public func value(for property: (O) -> TransformableContainer.Required) -> V { - - return self.rawObject.value(forKey: property(O.meta).keyPath)! as! V - } - - public func value(for property: (O) -> TransformableContainer.Optional) -> V? { - - return self.rawObject.value(forKey: property(O.meta).keyPath) as! V? - } - public func setValue(_ value: V, for property: (O) -> ValueContainer.Required) { self.rawObject.setValue( @@ -67,51 +54,11 @@ public struct PartialObject { ) } - public func setValue(_ value: V?, for property: (O) -> ValueContainer.Optional) { - - self.rawObject.setValue( - value?.cs_toImportableNativeType(), - forKey: property(O.meta).keyPath - ) - } - - public func setValue(_ value: V, for property: (O) -> TransformableContainer.Required) { - - self.rawObject.setValue( - value, - forKey: property(O.meta).keyPath - ) - } - - public func setValue(_ value: V?, for property: (O) -> TransformableContainer.Optional) { - - self.rawObject.setValue( - value, - forKey: property(O.meta).keyPath - ) - } - public func primitiveValue(for property: (O) -> ValueContainer.Required) -> V { return V.cs_fromImportableNativeType( self.rawObject.primitiveValue(forKey: property(O.meta).keyPath)! as! V.ImportableNativeType - )! - } - - public func primitiveValue(for property: (O) -> ValueContainer.Optional) -> V? { - - return (self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) as! V.ImportableNativeType?) - .flatMap(V.cs_fromImportableNativeType) - } - - public func primitiveValue(for property: (O) -> TransformableContainer.Required) -> V { - - return self.rawObject.primitiveValue(forKey: property(O.meta).keyPath)! as! V - } - - public func primitiveValue(for property: (O) -> TransformableContainer.Optional) -> V? { - - return self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) as! V? + )! } public func setPrimitiveValue(_ value: V, for property: (O) -> ValueContainer.Required) { @@ -122,6 +69,29 @@ public struct PartialObject { ) } + + // MARK: Value.Optional utilities + + public func value(for property: (O) -> ValueContainer.Optional) -> V? { + + return (self.rawObject.value(forKey: property(O.meta).keyPath) as! V.ImportableNativeType?) + .flatMap(V.cs_fromImportableNativeType) + } + + public func setValue(_ value: V?, for property: (O) -> ValueContainer.Optional) { + + self.rawObject.setValue( + value?.cs_toImportableNativeType(), + forKey: property(O.meta).keyPath + ) + } + + public func primitiveValue(for property: (O) -> ValueContainer.Optional) -> V? { + + return (self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) as! V.ImportableNativeType?) + .flatMap(V.cs_fromImportableNativeType) + } + public func setPrimitiveValue(_ value: V?, for property: (O) -> ValueContainer.Optional) { self.rawObject.setPrimitiveValue( @@ -130,6 +100,27 @@ public struct PartialObject { ) } + + // MARK: Transformable.Required utilities + + public func value(for property: (O) -> TransformableContainer.Required) -> V { + + return self.rawObject.value(forKey: property(O.meta).keyPath)! as! V + } + + public func setValue(_ value: V, for property: (O) -> TransformableContainer.Required) { + + self.rawObject.setValue( + value, + forKey: property(O.meta).keyPath + ) + } + + public func primitiveValue(for property: (O) -> TransformableContainer.Required) -> V { + + return self.rawObject.primitiveValue(forKey: property(O.meta).keyPath)! as! V + } + public func setPrimitiveValue(_ value: V, for property: (O) -> TransformableContainer.Required) { self.rawObject.setPrimitiveValue( @@ -138,6 +129,27 @@ public struct PartialObject { ) } + + // MARK: Transformable.Optional utilities + + public func value(for property: (O) -> TransformableContainer.Optional) -> V? { + + return self.rawObject.value(forKey: property(O.meta).keyPath) as! V? + } + + public func setValue(_ value: V?, for property: (O) -> TransformableContainer.Optional) { + + self.rawObject.setValue( + value, + forKey: property(O.meta).keyPath + ) + } + + public func primitiveValue(for property: (O) -> TransformableContainer.Optional) -> V? { + + return self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) as! V? + } + public func setPrimitiveValue(_ value: V?, for property: (O) -> TransformableContainer.Optional) { self.rawObject.setPrimitiveValue( From f5b3901caa7835efc38fa5017f4396431e45edce Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 17 Jun 2017 02:13:54 +0900 Subject: [PATCH 09/11] fix tests and demo app --- .../Palette.swift | 10 ++--- CoreStoreTests/DynamicModelTests.swift | 44 ++++++++++++------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift index 07fd502..dfa1b36 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift @@ -23,18 +23,18 @@ final class Palette: CoreStoreObject { let colorName = Value.Optional( "colorName", isTransient: true, - customGetter: Palette.getCachedColorName + customGetter: Palette.getColorName ) - private static func getCachedColorName(_ instance: Palette, _ getValue: () -> String?) -> String? { + private static func getColorName(_ partialObject: PartialObject) -> String? { - if let colorName = getValue() { + if let colorName = partialObject.primitiveValue(for: { $0.colorName }) { return colorName } let colorName: String - switch instance.hue.value % 360 { + switch partialObject.value(for: { $0.hue }) % 360 { case 0 ..< 20: colorName = "Lower Reds" case 20 ..< 57: colorName = "Oranges and Browns" @@ -47,7 +47,7 @@ final class Palette: CoreStoreObject { default: colorName = "Upper Reds" } - instance.colorName.primitiveValue = colorName + partialObject.setPrimitiveValue(colorName, for: { $0.colorName }) return colorName } } diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index b55b4bb..20081bf 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -51,40 +51,50 @@ class Dog: Animal { } class Person: CoreStoreObject { + let title = Value.Required( "title", default: "Mr.", - customSetter: { (`self`, setValue, originalNewValue) in - - setValue(originalNewValue) - self.displayName .= nil - } + customSetter: Person.setTitle ) + let name = Value.Required( "name", - customSetter: { (`self`, setValue, originalNewValue) in - - setValue(originalNewValue) - self.displayName .= nil - } + customSetter: Person.setName ) + let displayName = Value.Optional( "displayName", isTransient: true, - customGetter: Person.cachedDisplayName(_:_:), + customGetter: Person.getDisplayName(_:), affectedByKeyPaths: Person.keyPathsAffectingDisplayName() ) + let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) - static func cachedDisplayName(_ instance: Person, _ getValue: () -> String?) -> String? { + private static func setTitle(_ partialObject: PartialObject, _ newValue: String) { - if let cached = getValue() { + partialObject.setPrimitiveValue(newValue, for: { $0.title }) + partialObject.setPrimitiveValue(nil, for: { $0.displayName }) + } + + private static func setName(_ partialObject: PartialObject, _ newValue: String) { + + partialObject.setPrimitiveValue(newValue, for: { $0.name }) + partialObject.setPrimitiveValue(nil, for: { $0.displayName }) + } + + static func getDisplayName(_ partialObject: PartialObject) -> String? { + + if let displayName = partialObject.primitiveValue(for: { $0.displayName }) { - return cached + return displayName } - let primitiveValue = "\(instance.title.value) \(instance.name.value)" - instance.displayName .= primitiveValue - return primitiveValue + let title = partialObject.value(for: { $0.title }) + let name = partialObject.value(for: { $0.name }) + let displayName = "\(title) \(name)" + partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + return displayName } static func keyPathsAffectingDisplayName() -> Set { From d0c3203e63aee131551388c4af45a9968e9ee0ac Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 17 Jun 2017 21:50:21 +0900 Subject: [PATCH 10/11] force reset contexts after autocommit --- Sources/AsynchronousDataTransaction.swift | 1 + Sources/SynchronousDataTransaction.swift | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Sources/AsynchronousDataTransaction.swift b/Sources/AsynchronousDataTransaction.swift index 08caeab..c3c2e3c 100644 --- a/Sources/AsynchronousDataTransaction.swift +++ b/Sources/AsynchronousDataTransaction.swift @@ -210,6 +210,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { group.leave() } group.wait() + self.context.reset() } diff --git a/Sources/SynchronousDataTransaction.swift b/Sources/SynchronousDataTransaction.swift index d9a3f92..070a718 100644 --- a/Sources/SynchronousDataTransaction.swift +++ b/Sources/SynchronousDataTransaction.swift @@ -158,6 +158,10 @@ public final class SynchronousDataTransaction: BaseDataTransaction { self.isCommitted = true let result = self.context.saveSynchronously(waitForMerge: waitForMerge) self.result = result + defer { + + self.context.reset() + } return result } From e8eb309d8291a8b487ab52a0b3bcd0d463dae79f Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 20 Jun 2017 20:32:27 +0900 Subject: [PATCH 11/11] Added source docs on usage of custom getters and setters for CoreStoreObject properties (Value.Required, etc) --- Sources/PartialObject.swift | 5 +- Sources/Value.swift | 131 ++++++++++++++++++++++++------------ 2 files changed, 93 insertions(+), 43 deletions(-) diff --git a/Sources/PartialObject.swift b/Sources/PartialObject.swift index b103d21..49b0b2c 100644 --- a/Sources/PartialObject.swift +++ b/Sources/PartialObject.swift @@ -29,6 +29,9 @@ import Foundation // MARK: - PartialObject +/** + A `PartialObject` is only used when overriding getters and setters for `CoreStoreObject` properties. Custom getters and setters are implemented as a closure that "overrides" the default property getter/setter. The closure receives a `PartialObject`, 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 heavy performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.persistentValue(for:)` instead of `PartialObject.value(for:)`, which would unintentionally execute the same closure again recursively. + */ public struct PartialObject { public func completeObject() -> O { @@ -37,7 +40,7 @@ public struct PartialObject { } - // MARK: Value.Required utilities + // MARK: Value.Required accessors/mutators public func value(for property: (O) -> ValueContainer.Required) -> V { diff --git a/Sources/Value.swift b/Sources/Value.swift index a16e160..c329ea4 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -93,12 +93,24 @@ public enum ValueContainer { ``` class Person: CoreStoreObject { let title = Value.Required("title", default: "Mr.") - let name = Value.Required( - "name", - customGetter: { (`self`, getValue) in - return "\(self.title.value) \(getValue())" - } + let name = Value.Required("name") + let displayName = Value.Required( + "displayName", + isTransient: true, + customGetter: Person.getName(_:) ) + + private static func getName(_ partialObject: PartialObject) -> String { + let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) + if !cachedDisplayName.isEmpty { + return cachedDisplayName + } + let title = partialObject.value(for: { $0.title }) + let name = partialObject.value(for: { $0.name }) + let displayName = "\(title) \(name)" + partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + return displayName + } } ``` - parameter keyPath: the permanent attribute name for this property. @@ -107,13 +119,8 @@ public enum ValueContainer { - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. - - parameter self: the `CoreStoreObject` - - parameter getValue: the original getter for the property - - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. - - parameter setValue: the original setter for the property - - parameter finalNewValue: the transformed new value - - parameter originalNewValue: the original new value + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject`, 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`, make sure to use `PartialObject.primitiveValue(for:)` instead of `PartialObject.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`, 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`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.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( @@ -280,13 +287,24 @@ public enum ValueContainer { Initializes the metadata for the property. ``` class Person: CoreStoreObject { - let title = Value.Required("title", default: "Mr.") - let name = Value.Required( - "name", - customGetter: { (`self`, getValue) in - return "\(self.title.value) \(getValue())" - } + let title = Value.Optional("title", default: "Mr.") + let name = Value.Optional("name") + let displayName = Value.Optional( + "displayName", + isTransient: true, + customGetter: Person.getName(_:) ) + + private static func getName(_ partialObject: PartialObject) -> String? { + if let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) { + return cachedDisplayName + } + let title = partialObject.value(for: { $0.title }) + let name = partialObject.value(for: { $0.name }) + let displayName = "\(title) \(name)" + partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + return displayName + } } ``` - parameter keyPath: the permanent attribute name for this property. @@ -453,7 +471,8 @@ public extension ValueContainer.Required where V: EmptyableAttributeType { Initializes the metadata for the property. This convenience initializer uses the `EmptyableAttributeType`'s "empty" value as the initial value for the property when the object is first created (e.g. `false` for `Bool`, `0` for `Int`, `""` for `String`, etc.) ``` class Person: CoreStoreObject { - let title = Value.Required("title") // initial value defaults to empty string + let title = Value.Required("title", default: "Mr.") // explicit default value + let name = Value.Required("name") // initial value defaults to empty string } ``` - parameter keyPath: the permanent attribute name for this property. @@ -461,13 +480,8 @@ public extension ValueContainer.Required where V: EmptyableAttributeType { - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. - - parameter self: the `CoreStoreObject` - - parameter getValue: the original getter for the property - - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. - - parameter setValue: the original setter for the property - - parameter finalNewValue: the transformed new value - - parameter originalNewValue: the original new value + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject`, 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`, make sure to use `PartialObject.primitiveValue(for:)` instead of `PartialObject.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`, 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`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.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( @@ -528,7 +542,30 @@ public enum TransformableContainer { Initializes the metadata for the property. ``` class Animal: CoreStoreObject { - let color = Transformable.Optional("color") + let species = Value.Required("species") + let color = Transformable.Required( + "color", + default: UIColor.clear, + isTransient: true, + customGetter: Animal.getColor(_:) + ) + } + + private static func getColor(_ partialObject: PartialObject) -> 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. @@ -537,13 +574,8 @@ public enum TransformableContainer { - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. - - parameter self: the `CoreStoreObject` - - parameter getValue: the original getter for the property - - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. - - parameter setValue: the original setter for the property - - parameter finalNewValue: the transformed new value - - parameter originalNewValue: the original new value + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject`, 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`, make sure to use `PartialObject.primitiveValue(for:)` instead of `PartialObject.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`, 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`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.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( @@ -708,7 +740,27 @@ public enum TransformableContainer { Initializes the metadata for the property. ``` class Animal: CoreStoreObject { - let color = Transformable.Optional("color") + let species = Value.Required("species") + let color = Transformable.Optional( + "color", + isTransient: true, + customGetter: Animal.getColor(_:) + ) + } + + private static func getColor(_ partialObject: PartialObject) -> 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. @@ -717,13 +769,8 @@ public enum TransformableContainer { - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. - - parameter self: the `CoreStoreObject` - - parameter getValue: the original getter for the property - - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. - - parameter setValue: the original setter for the property - - parameter finalNewValue: the transformed new value - - parameter originalNewValue: the original new value + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject`, 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`, make sure to use `PartialObject.primitiveValue(for:)` instead of `PartialObject.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`, 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`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.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(