From 801cf8d9f028e55258d960c373f04d6115e3e2ec Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 14 Jun 2017 17:37:46 +0900 Subject: [PATCH] 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