diff --git a/Sources/DynamicObject.swift b/Sources/DynamicObject.swift index 5905216..3f09383 100644 --- a/Sources/DynamicObject.swift +++ b/Sources/DynamicObject.swift @@ -172,10 +172,17 @@ extension CoreStoreObject { switch child.value { case let property as FieldAttributeProtocol: - attributes[property.keyPath] = type(of: property).read(field: property, for: object.rawObject!) + attributes[property.keyPath] = type(of: property).read( + field: property, + for: object.rawObject!, + bypassThreadCheck: false + ) case let property as FieldRelationshipProtocol: - attributes[property.keyPath] = type(of: property).valueForSnapshot(field: property, for: object.rawObject!) + attributes[property.keyPath] = type(of: property).valueForSnapshot( + field: property, + for: object.rawObject! + ) case let property as AttributeProtocol: attributes[property.keyPath] = property.valueForSnapshot @@ -212,7 +219,11 @@ extension CoreStoreObject { switch property { case let property as FieldAttributeProtocol: - values[property.keyPath] = type(of: property).read(field: property, for: rawObject) + values[property.keyPath] = type(of: property).read( + field: property, + for: rawObject, + bypassThreadCheck: false + ) default: continue diff --git a/Sources/Field.Coded.swift b/Sources/Field.Coded.swift index c79699a..d94e813 100644 --- a/Sources/Field.Coded.swift +++ b/Sources/Field.Coded.swift @@ -82,7 +82,7 @@ extension FieldContainer { - parameter keyPath: the permanent attribute name for this property. - 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 previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` 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 `keyPath`. - - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.primitiveValue(for:)` instead of `ObjectProxy.value(for:)`, which would unintentionally execute the same closure again recursively. + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, 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:)`. */ @@ -161,7 +161,7 @@ extension FieldContainer { instance.rawObject != nil, "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) - return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!) as! V + return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!, bypassThreadCheck: false) as! V } set { @@ -200,10 +200,10 @@ extension FieldContainer { // MARK: FieldProtocol - internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? { + internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject, bypassThreadCheck: Bool) -> Any? { Internals.assert( - rawObject.isRunningInAllowedQueue() == true, + bypassThreadCheck || rawObject.isRunningInAllowedQueue() == true, "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." ) let field = field as! Self @@ -397,7 +397,7 @@ extension FieldContainer.Coded where V: FieldOptionalType { - parameter keyPath: the permanent attribute name for this property. - 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 previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` 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 `keyPath`. - - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.primitiveValue(for:)` instead of `ObjectProxy.value(for:)`, which would unintentionally execute the same closure again recursively. + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, 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:)`. */ @@ -485,7 +485,7 @@ extension FieldContainer.Coded where V: DefaultNSSecureCodable { - parameter keyPath: the permanent attribute name for this property. - 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 previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` 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 `keyPath`. - - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.primitiveValue(for:)` instead of `ObjectProxy.value(for:)`, which would unintentionally execute the same closure again recursively. + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, 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:)`. */ diff --git a/Sources/Field.Relationship.swift b/Sources/Field.Relationship.swift index 630941b..f590327 100644 --- a/Sources/Field.Relationship.swift +++ b/Sources/Field.Relationship.swift @@ -99,7 +99,7 @@ extension FieldContainer { instance.rawObject != nil, "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) - return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!) as! V + return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!, bypassThreadCheck: false) as! V } set { @@ -138,10 +138,10 @@ extension FieldContainer { // MARK: FieldProtocol - internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? { + internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject, bypassThreadCheck: Bool) -> Any? { Internals.assert( - rawObject.isRunningInAllowedQueue() == true, + bypassThreadCheck || rawObject.isRunningInAllowedQueue() == true, "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." ) let field = field as! Self diff --git a/Sources/Field.Stored.swift b/Sources/Field.Stored.swift index a3bc8dd..29f5742 100644 --- a/Sources/Field.Stored.swift +++ b/Sources/Field.Stored.swift @@ -75,7 +75,7 @@ extension FieldContainer { - parameter keyPath: the permanent attribute name for this property. - 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 previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` 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 `keyPath`. - - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.primitiveValue(for:)` instead of `ObjectProxy.value(for:)`, which would unintentionally execute the same closure again recursively. + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, 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:)`. */ @@ -128,7 +128,7 @@ extension FieldContainer { instance.rawObject != nil, "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) - return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!) as! V + return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!, bypassThreadCheck: false) as! V } set { @@ -167,10 +167,10 @@ extension FieldContainer { // MARK: FieldProtocol - internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? { + internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject, bypassThreadCheck: Bool) -> Any? { Internals.assert( - rawObject.isRunningInAllowedQueue() == true, + bypassThreadCheck || rawObject.isRunningInAllowedQueue() == true, "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." ) let field = field as! Self @@ -344,7 +344,7 @@ extension FieldContainer.Stored where V: FieldOptionalType { - parameter keyPath: the permanent attribute name for this property. - 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 previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` 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 `keyPath`. - - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.primitiveValue(for:)` instead of `ObjectProxy.value(for:)`, which would unintentionally execute the same closure again recursively. + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, 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:)`. */ diff --git a/Sources/Field.Virtual.swift b/Sources/Field.Virtual.swift index ad36970..815e18d 100644 --- a/Sources/Field.Virtual.swift +++ b/Sources/Field.Virtual.swift @@ -79,7 +79,7 @@ extension FieldContainer { } ``` - parameter keyPath: the permanent attribute name for this property. - - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.primitiveValue(for:)` instead of `ObjectProxy.value(for:)`, which would unintentionally execute the same closure again recursively. + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example. - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy`, 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 `ObjectProxy`, make sure to use `ObjectProxy.$property.primitiveValue` instead of `ObjectProxy.$property.value`, 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:)`. */ @@ -139,7 +139,7 @@ extension FieldContainer { instance.rawObject != nil, "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) - return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!) as! V + return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!, bypassThreadCheck: false) as! V } set { @@ -178,10 +178,10 @@ extension FieldContainer { // MARK: FieldProtocol - internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? { + internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject, bypassThreadCheck: Bool) -> Any? { Internals.assert( - rawObject.isRunningInAllowedQueue() == true, + bypassThreadCheck || rawObject.isRunningInAllowedQueue() == true, "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." ) let field = field as! Self diff --git a/Sources/FieldProtocol.swift b/Sources/FieldProtocol.swift index 9ae3014..77b1981 100644 --- a/Sources/FieldProtocol.swift +++ b/Sources/FieldProtocol.swift @@ -31,6 +31,6 @@ import CoreData internal protocol FieldProtocol: PropertyProtocol { - static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? + static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject, bypassThreadCheck: Bool) -> Any? static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) } diff --git a/Sources/ObjectProxy.swift b/Sources/ObjectProxy.swift index cd2f61e..89e233e 100644 --- a/Sources/ObjectProxy.swift +++ b/Sources/ObjectProxy.swift @@ -133,7 +133,11 @@ public struct ObjectProxy { let keyPathString = field.keyPath self.getValue = { - return type(of: field).read(field: field, for: rawObject) as! V + return type(of: field).read( + field: field, + for: rawObject, + bypassThreadCheck: true // May be called from NSError logs + ) as! V } self.setValue = { @@ -159,7 +163,11 @@ public struct ObjectProxy { let keyPathString = field.keyPath self.getValue = { - return type(of: field).read(field: field, for: rawObject) as! V + return type(of: field).read( + field: field, + for: rawObject, + bypassThreadCheck: true // May be called from NSError logs + ) as! V } self.setValue = { @@ -192,7 +200,11 @@ public struct ObjectProxy { let keyPathString = field.keyPath self.getValue = { - return type(of: field).read(field: field, for: rawObject) as! V + return type(of: field).read( + field: field, + for: rawObject, + bypassThreadCheck: true // May be called from NSError logs + ) as! V } self.setValue = { diff --git a/Sources/ObjectPublisher.swift b/Sources/ObjectPublisher.swift index 286e0bc..23893c1 100644 --- a/Sources/ObjectPublisher.swift +++ b/Sources/ObjectPublisher.swift @@ -380,7 +380,7 @@ extension ObjectPublisher where O: CoreStoreObject { return nil } let field = object[keyPath: member] - return type(of: field).read(field: field, for: rawObject) as! V? + return type(of: field).read(field: field, for: rawObject, bypassThreadCheck: false) as! V? } /** @@ -396,7 +396,7 @@ extension ObjectPublisher where O: CoreStoreObject { return nil } let field = object[keyPath: member] - return type(of: field).read(field: field, for: rawObject) as! V? + return type(of: field).read(field: field, for: rawObject, bypassThreadCheck: false) as! V? } /** @@ -412,7 +412,7 @@ extension ObjectPublisher where O: CoreStoreObject { return nil } let field = object[keyPath: member] - return type(of: field).read(field: field, for: rawObject) as! V? + return type(of: field).read(field: field, for: rawObject, bypassThreadCheck: false) as! V? } /** @@ -428,7 +428,7 @@ extension ObjectPublisher where O: CoreStoreObject { return nil } let field = object[keyPath: member] - guard let value = type(of: field).read(field: field, for: rawObject) as! V? else { + guard let value = type(of: field).read(field: field, for: rawObject, bypassThreadCheck: false) as! V? else { return nil }