From fe135acbec386d1190d1b8cc0585a87c0837ecf1 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 10 Jun 2017 21:02:36 +0900 Subject: [PATCH] Improve CoreStoreObjects KVO behavior --- CoreStoreTests/DynamicModelTests.swift | 62 +- Sources/CoreStoreManagedObject.swift | 28 +- Sources/CoreStoreObject.swift | 8 +- Sources/CoreStoreSchema.swift | 141 +++- .../NSEntityDescription+DynamicModel.swift | 28 +- Sources/Relationship.swift | 164 +++-- Sources/Value.swift | 606 +++++++++++++----- 7 files changed, 743 insertions(+), 294 deletions(-) diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index b63c708..b55b4bb 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -51,15 +51,49 @@ class Dog: Animal { } class Person: CoreStoreObject { - let title = Value.Required("title", default: "Mr.") - let name = Value.Required( - "name", - customGetter: { (`self`, getValue) in + let title = Value.Required( + "title", + default: "Mr.", + customSetter: { (`self`, setValue, originalNewValue) in - return "\(self.title.value) \(getValue())" + setValue(originalNewValue) + self.displayName .= nil } ) + let name = Value.Required( + "name", + customSetter: { (`self`, setValue, originalNewValue) in + + setValue(originalNewValue) + self.displayName .= nil + } + ) + let displayName = Value.Optional( + "displayName", + isTransient: true, + customGetter: Person.cachedDisplayName(_:_:), + affectedByKeyPaths: Person.keyPathsAffectingDisplayName() + ) let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) + + static func cachedDisplayName(_ instance: Person, _ getValue: () -> String?) -> String? { + + if let cached = getValue() { + + return cached + } + let primitiveValue = "\(instance.title.value) \(instance.name.value)" + instance.displayName .= primitiveValue + return primitiveValue + } + + static func keyPathsAffectingDisplayName() -> Set { + + return [ + self.keyPath({ $0.title }), + self.keyPath({ $0.name }) + ] + } } @@ -124,11 +158,25 @@ class DynamicModelTests: BaseTestDataTestCase { let person = transaction.create(Into()) XCTAssertTrue(person.pets.value.isEmpty) + XCTAssertEqual( + object_getClass(person.rawObject!).keyPathsForValuesAffectingValue(forKey: "displayName"), + ["title", "name"] + ) + + person.name .= "Joe" + + XCTAssertEqual(person.rawObject!.value(forKey: "name") as! String?, "Joe") + XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "Mr. Joe") + + person.rawObject!.setValue("AAAA", forKey: "displayName") + XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "AAAA") + person.name .= "John" - XCTAssertEqual(person.name.value, "Mr. John") // Custom getter + XCTAssertEqual(person.name.value, "John") + XCTAssertEqual(person.displayName.value, "Mr. John") // Custom getter person.title .= "Sir" - XCTAssertEqual(person.name.value, "Sir John") + XCTAssertEqual(person.displayName.value, "Sir John") person.pets.value.insert(dog) XCTAssertEqual(person.pets.count, 1) diff --git a/Sources/CoreStoreManagedObject.swift b/Sources/CoreStoreManagedObject.swift index 285596e..0c8f02d 100644 --- a/Sources/CoreStoreManagedObject.swift +++ b/Sources/CoreStoreManagedObject.swift @@ -13,35 +13,15 @@ import CoreData @objc internal class CoreStoreManagedObject: NSManagedObject { + internal typealias CustomGetter = @convention(block) (_ rawObject: Any) -> Any? + internal typealias CustomSetter = @convention(block) (_ rawObject: Any, _ newValue: Any?) -> Void + internal typealias CustomGetterSetter = (getter: CustomGetter?, setter: CustomSetter?) + @nonobjc @inline(__always) internal static func cs_subclassName(for entity: DynamicEntity, in modelVersion: ModelVersion) -> String { return "_\(NSStringFromClass(CoreStoreManagedObject.self))__\(modelVersion)__\(NSStringFromClass(entity.type))__\(entity.entityName)" } - - @nonobjc - internal class func cs_setKeyPathsForValuesAffectingKeys(_ keyPathsForValuesAffectingKeys: [KeyPath: Set], for managedObjectClass: CoreStoreManagedObject.Type) { - - Static.queue.sync(flags: .barrier) { - - Static.cache[ObjectIdentifier(managedObjectClass)] = keyPathsForValuesAffectingKeys - } - } - - // MARK: NSManagedObject - - override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set { - - return Static.queue.sync(flags: .barrier) { - - let cacheKey = ObjectIdentifier(self) - if let keyPathsForValuesAffectingKeys = Static.cache[cacheKey] { - - return keyPathsForValuesAffectingKeys[key] ?? [] - } - return super.keyPathsForValuesAffectingValue(forKey: key) - } - } } diff --git a/Sources/CoreStoreObject.swift b/Sources/CoreStoreObject.swift index f1751e8..910644f 100644 --- a/Sources/CoreStoreObject.swift +++ b/Sources/CoreStoreObject.swift @@ -68,8 +68,8 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable { public required init(rawObject: NSManagedObject) { self.isMeta = false - self.rawObject = rawObject - self.initializeAttributes(Mirror(reflecting: self), { [unowned self] in self }) + self.rawObject = (rawObject as! CoreStoreManagedObject) + self.initializeAttributes(Mirror(reflecting: self), self) } /** @@ -110,13 +110,13 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable { // MARK: Internal - internal let rawObject: NSManagedObject? + internal let rawObject: CoreStoreManagedObject? internal let isMeta: Bool // MARK: Private - private func initializeAttributes(_ mirror: Mirror, _ parentObject: @escaping () -> CoreStoreObject) { + private func initializeAttributes(_ mirror: Mirror, _ parentObject: CoreStoreObject) { _ = mirror.superclassMirror.flatMap({ self.initializeAttributes($0, parentObject) }) for child in mirror.children { diff --git a/Sources/CoreStoreSchema.swift b/Sources/CoreStoreSchema.swift index 32b1d0f..11a4a9c 100644 --- a/Sources/CoreStoreSchema.swift +++ b/Sources/CoreStoreSchema.swift @@ -208,17 +208,22 @@ public final class CoreStoreSchema: DynamicSchema { } let rawModel = NSManagedObjectModel() var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:] + var allCustomGettersSetters: [DynamicEntity: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]] = [:] for entity in self.allEntities { - let entityDescription = self.entityDescription( + let (entityDescription, customGetterSetterByKeyPaths) = self.entityDescription( for: entity, initializer: CoreStoreSchema.firstPassCreateEntityDescription(from:in:) ) entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription) + allCustomGettersSetters[entity] = customGetterSetterByKeyPaths } CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity) CoreStoreSchema.thirdPassConnectInheritanceTree(for: entityDescriptionsByEntity) - CoreStoreSchema.fourthPassSynthesizeManagedObjectClasses(for: entityDescriptionsByEntity) + CoreStoreSchema.fourthPassSynthesizeManagedObjectClasses( + for: entityDescriptionsByEntity, + allCustomGettersSetters: allCustomGettersSetters + ) rawModel.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! }) for (configuration, entities) in self.entitiesByConfiguration { @@ -248,21 +253,23 @@ public final class CoreStoreSchema: DynamicSchema { private let allEntities: Set private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:] + private var customGettersSettersByEntity: [DynamicEntity: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]] = [:] private weak var cachedRawModel: NSManagedObjectModel? - private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity, ModelVersion) -> NSEntityDescription) -> NSEntityDescription { + private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity, ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter])) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]) { if let cachedEntityDescription = self.entityDescriptionsByEntity[entity] { - return cachedEntityDescription + return (cachedEntityDescription, self.customGettersSettersByEntity[entity] ?? [:]) } let modelVersion = self.modelVersion - let entityDescription = withoutActuallyEscaping(initializer, do: { $0(entity, modelVersion) }) + let (entityDescription, customGetterSetterByKeyPaths) = withoutActuallyEscaping(initializer, do: { $0(entity, modelVersion) }) self.entityDescriptionsByEntity[entity] = entityDescription - return entityDescription + self.customGettersSettersByEntity[entity] = customGetterSetterByKeyPaths + return (entityDescription, customGetterSetterByKeyPaths) } - private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> NSEntityDescription { + private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]) { let entityDescription = NSEntityDescription() entityDescription.coreStoreEntity = entity @@ -272,6 +279,7 @@ public final class CoreStoreSchema: DynamicSchema { entityDescription.managedObjectClassName = CoreStoreManagedObject.cs_subclassName(for: entity, in: modelVersion) var keyPathsByAffectedKeyPaths: [KeyPath: Set] = [:] + var customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter] = [:] func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] { var propertyDescriptions: [NSPropertyDescription] = [] @@ -285,12 +293,13 @@ public final class CoreStoreSchema: DynamicSchema { description.attributeType = type(of: attribute).attributeType description.isOptional = attribute.isOptional description.isIndexed = attribute.isIndexed - description.defaultValue = attribute.defaultValue + description.defaultValue = attribute.defaultValue() description.isTransient = attribute.isTransient description.versionHashModifier = attribute.versionHashModifier description.renamingIdentifier = attribute.renamingIdentifier propertyDescriptions.append(description) keyPathsByAffectedKeyPaths[attribute.keyPath] = attribute.affectedByKeyPaths() + customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter) case let relationship as RelationshipProtocol: let description = NSRelationshipDescription() @@ -310,9 +319,9 @@ public final class CoreStoreSchema: DynamicSchema { } return propertyDescriptions } - entityDescription.keyPathsByAffectedKeyPaths = keyPathsByAffectedKeyPaths entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type) - return entityDescription + entityDescription.keyPathsByAffectedKeyPaths = keyPathsByAffectedKeyPaths + return (entityDescription, customGetterSetterByKeyPaths) } private static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) { @@ -434,9 +443,9 @@ public final class CoreStoreSchema: DynamicSchema { } } - private static func fourthPassSynthesizeManagedObjectClasses(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) { + private static func fourthPassSynthesizeManagedObjectClasses(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription], allCustomGettersSetters: [DynamicEntity: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]]) { - func createManagedObjectSubclass(for entityDescription: NSEntityDescription) { + func createManagedObjectSubclass(for entityDescription: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]?) { let superEntity = entityDescription.superentity let className = entityDescription.managedObjectClassName! @@ -446,7 +455,10 @@ public final class CoreStoreSchema: DynamicSchema { } if let superEntity = superEntity { - createManagedObjectSubclass(for: superEntity) + createManagedObjectSubclass( + for: superEntity, + customGetterSetterByKeyPaths: superEntity.coreStoreEntity.flatMap({ allCustomGettersSetters[$0] }) + ) } let superClass = cs_lazy { () -> CoreStoreManagedObject.Type in @@ -457,19 +469,102 @@ public final class CoreStoreSchema: DynamicSchema { } return CoreStoreManagedObject.self } - let managedObjectClass = className.withCString { + let managedObjectClass: AnyClass = className.withCString { - return objc_allocateClassPair(superClass, $0, 0) as! CoreStoreManagedObject.Type + return objc_allocateClassPair(superClass, $0, 0)! } - objc_registerClassPair(managedObjectClass) - managedObjectClass.cs_setKeyPathsForValuesAffectingKeys( - entityDescription.keyPathsByAffectedKeyPaths, - for: managedObjectClass - ) - } - for (_, entityDescription) in entityDescriptionsByEntity { + defer { - createManagedObjectSubclass(for: entityDescription) + objc_registerClassPair(managedObjectClass) + } + + func capitalize(_ string: String) -> String { + + return string.replacingCharacters( + in: Range(uncheckedBounds: (string.startIndex, string.index(after: string.startIndex))), + with: String(string[string.startIndex]).uppercased() + ) + } + for (attributeName, customGetterSetters) in (customGetterSetterByKeyPaths ?? [:]) + where customGetterSetters.getter != nil || customGetterSetters.setter != nil { + + var rawAttributes: [objc_property_attribute_t] = [ + objc_property_attribute_t(name: "T@", value: attributeName), + objc_property_attribute_t(name: "N", value: ""), + objc_property_attribute_t(name: "C", value: ""), + objc_property_attribute_t(name: "&", value: "") + ] + if let getter = customGetterSetters.getter { + + let getterName = "\(attributeName)" + guard class_addMethod( + managedObjectClass, + NSSelectorFromString(getterName), + imp_implementationWithBlock(getter), + "@@:") else { + + CoreStore.abort("Could not dynamically add getter method \"\(getterName)\" to class \(cs_typeName(managedObjectClass))") + } + rawAttributes.append(objc_property_attribute_t(name: "G", value: getterName)) + } + if let setter = customGetterSetters.setter { + + let setterName = "set\(capitalize(attributeName)):" + guard class_addMethod( + managedObjectClass, + NSSelectorFromString(setterName), + imp_implementationWithBlock(setter), + "v@:@") else { + + CoreStore.abort("Could not dynamically add setter method \"\(setterName)\" to class \(cs_typeName(managedObjectClass))") + } + rawAttributes.append(objc_property_attribute_t(name: "S", value: setterName)) + } +// rawAttributes.append("\(attributeName)".withCString({ objc_property_attribute_t(name: "V", value: $0) })) + +// rawAttributes.withUnsafeBufferPointer { (buffer) in +// +// guard class_addProperty(managedObjectClass, attributeName, buffer.baseAddress, UInt32(buffer.count)) else { +// +// CoreStore.abort("Could not dynamically add property \"\(attributeName)\" to class \(cs_typeName(managedObjectClass))") +// } +// } + } + + let newSelector = NSSelectorFromString("cs_keyPathsForValuesAffectingValueForKey:") + let keyPathsByAffectedKeyPaths = entityDescription.keyPathsByAffectedKeyPaths + let keyPathsForValuesAffectingValue: @convention(block) (Any, String) -> Set = { (instance, keyPath) in + + if let keyPaths = keyPathsByAffectedKeyPaths[keyPath] { + + return keyPaths + } + return [] + } + let origSelector = #selector(NSManagedObject.keyPathsForValuesAffectingValue(forKey:)) + + let metaClass: AnyClass = object_getClass(managedObjectClass)! + let origMethod = class_getClassMethod(managedObjectClass, origSelector) + + let origImp = method_getImplementation(origMethod) + let newImp = imp_implementationWithBlock(keyPathsForValuesAffectingValue) + + if class_addMethod(metaClass, origSelector, newImp, method_getTypeEncoding(origMethod)) { + + class_replaceMethod(metaClass, newSelector, origImp, method_getTypeEncoding(origMethod)) + } + else { + + let newMethod = class_getClassMethod(managedObjectClass, newSelector) + method_exchangeImplementations(origMethod, newMethod) + } + } + for (dynamicEntity, entityDescription) in entityDescriptionsByEntity { + + createManagedObjectSubclass( + for: entityDescription, + customGetterSetterByKeyPaths: allCustomGettersSetters[dynamicEntity] + ) } } } diff --git a/Sources/NSEntityDescription+DynamicModel.swift b/Sources/NSEntityDescription+DynamicModel.swift index bcf7bd8..52d2783 100644 --- a/Sources/NSEntityDescription+DynamicModel.swift +++ b/Sources/NSEntityDescription+DynamicModel.swift @@ -75,14 +75,15 @@ internal extension NSEntityDescription { } } + @nonobjc internal var keyPathsByAffectedKeyPaths: [KeyPath: Set] { get { if let userInfo = self.userInfo, - let function = userInfo[UserInfoKey.CoreStoreManagedObjectKeyPathsByAffectedKeyPaths] as! [KeyPath: Set]? { + let value = userInfo[UserInfoKey.CoreStoreManagedObjectKeyPathsByAffectedKeyPaths] { - return function + return value as! [KeyPath: Set] } return [:] } @@ -95,6 +96,27 @@ internal extension NSEntityDescription { } } + @nonobjc + internal var customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter] { + + get { + + if let userInfo = self.userInfo, + let value = userInfo[UserInfoKey.CoreStoreManagedObjectCustomGetterSetterByKeyPaths] { + + return value as! [KeyPath: CoreStoreManagedObject.CustomGetterSetter] + } + return [:] + } + set { + + cs_setUserInfo { (userInfo) in + + userInfo[UserInfoKey.CoreStoreManagedObjectCustomGetterSetterByKeyPaths] = newValue + } + } + } + // MARK: Private @@ -108,6 +130,8 @@ internal extension NSEntityDescription { fileprivate static let CoreStoreManagedObjectVersionHashModifier = "CoreStoreManagedObjectVersionHashModifier" fileprivate static let CoreStoreManagedObjectKeyPathsByAffectedKeyPaths = "CoreStoreManagedObjectKeyPathsByAffectedKeyPaths" + fileprivate static let CoreStoreManagedObjectCustomGetterSetterByKeyPaths = "CoreStoreManagedObjectCustomGetterSetterByKeyPaths" + } private func cs_setUserInfo(_ closure: (_ userInfo: inout [AnyHashable: Any]) -> Void) { diff --git a/Sources/Relationship.swift b/Sources/Relationship.swift index 88606be..848ae8f 100644 --- a/Sources/Relationship.swift +++ b/Sources/Relationship.swift @@ -245,41 +245,49 @@ public enum RelationshipContainer { internal let versionHashModifier: String? internal let renamingIdentifier: String? internal let affectedByKeyPaths: () -> Set - - internal var parentObject: () -> CoreStoreObject = { - - CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") - } + internal weak var parentObject: CoreStoreObject? internal var nativeValue: NSManagedObject? { get { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { $0 as! NSManagedObject? } + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { $0 as! NSManagedObject? } + ) + } } set { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - CoreStore.assert( - object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." - ) - object.rawObject!.setValue( - newValue, - forKvcKey: self.keyPath + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath + ) + } } } @@ -506,41 +514,49 @@ public enum RelationshipContainer { internal let versionHashModifier: String? internal let renamingIdentifier: String? internal let affectedByKeyPaths: () -> Set - - internal var parentObject: () -> CoreStoreObject = { - - CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") - } + internal weak var parentObject: CoreStoreObject? internal var nativeValue: NSOrderedSet { get { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { ($0 as! NSOrderedSet?) ?? [] } + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { ($0 as! NSOrderedSet?) ?? [] } + ) + } } set { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - CoreStore.assert( - object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." - ) - object.rawObject!.setValue( - newValue, - forKvcKey: self.keyPath + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath + ) + } } } @@ -772,41 +788,49 @@ public enum RelationshipContainer { internal let versionHashModifier: String? internal let renamingIdentifier: String? internal let affectedByKeyPaths: () -> Set - - internal var parentObject: () -> CoreStoreObject = { - - CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") - } + internal weak var parentObject: CoreStoreObject? internal var nativeValue: NSSet { get { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { ($0 as! NSSet?) ?? [] } + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { ($0 as! NSSet?) ?? [] } + ) + } } set { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - CoreStore.assert( - object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." - ) - object.rawObject!.setValue( - newValue, - forKvcKey: self.keyPath + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath + ) + } } } @@ -1192,7 +1216,7 @@ internal protocol RelationshipProtocol: class { var deleteRule: NSDeleteRule { get } var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get } var affectedByKeyPaths: () -> Set { get } - var parentObject: () -> CoreStoreObject { get set } + weak var parentObject: CoreStoreObject? { get set } var versionHashModifier: String? { get } var renamingIdentifier: String? { get } var minCount: Int { get } diff --git a/Sources/Value.swift b/Sources/Value.swift index 17b9941..758836e 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -118,19 +118,19 @@ public enum ValueContainer { */ public init( _ keyPath: KeyPath, - `default`: V, + `default`: @autoclosure @escaping () -> V, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, - customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void = { $1($2) }, + customGetter: ((_ `self`: O, _ getValue: () -> V) -> V)? = nil, + customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath self.isIndexed = isIndexed self.isTransient = isTransient - self.defaultValue = `default`.cs_toImportableNativeType() + self.defaultValue = { `default`().cs_toImportableNativeType() } self.versionHashModifier = versionHashModifier self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter @@ -145,45 +145,59 @@ public enum ValueContainer { get { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - return self.customGetter( - object, - { () -> V in - - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType)! } - ) - } + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + let customGetter = (self.customGetter ?? { $1() }) + return customGetter( + object, + { () -> V in + + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType)! } + ) + } + ) + } } set { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - CoreStore.assert( - object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." - ) - self.customSetter( - object, - { (newValue: V) -> Void in - - object.rawObject!.setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { $0.cs_toImportableNativeType() } - ) - }, - newValue + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + let customSetter = (self.customSetter ?? { $1($2) }) + customSetter( + object, + { (newValue: V) -> Void in + + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { $0.cs_toImportableNativeType() } + ) + }, + newValue + ) + } } } @@ -200,21 +214,81 @@ public enum ValueContainer { internal let isOptional = false internal let isIndexed: Bool internal let isTransient: Bool - internal let defaultValue: Any? internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let defaultValue: () -> Any? internal let affectedByKeyPaths: () -> Set + internal weak var parentObject: CoreStoreObject? - internal var parentObject: () -> CoreStoreObject = { + internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in - CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") + let keyPath = self.keyPath + guard let customGetter = self.customGetter else { + + guard let _ = self.customSetter else { + + return nil + } + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + return rawObject.getValue(forKvcKey: keyPath) + } + } + 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!)! } + ) + } + ) + return value.cs_toImportableNativeType() + } + } + + 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 { (_ id: Any, _ newValue: Any?) -> Void in + + let rawObject = id as! CoreStoreManagedObject + customSetter( + O.cs_fromRaw(object: rawObject), + { (userValue: V) -> Void in + + rawObject.setValue( + userValue, + forKvcKey: keyPath, + willSetValue: { $0.cs_toImportableNativeType() } + ) + }, + V.cs_fromImportableNativeType(newValue as! V.ImportableNativeType)! + ) + } } // MARK: Private - private let customGetter: (_ `self`: O, _ getValue: () -> V) -> V - private let customSetter: (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void + private let customGetter: ((_ `self`: O, _ getValue: () -> V) -> V)? + private let customSetter: ((_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void)? } @@ -263,19 +337,19 @@ public enum ValueContainer { */ public init( _ keyPath: KeyPath, - `default`: V? = nil, + `default`: @autoclosure @escaping () -> V? = nil, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, - customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void = { $1($2) }, + customGetter: ((_ `self`: O, _ getValue: () -> V?) -> V?)? = nil, + customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath self.isIndexed = isIndexed self.isTransient = isTransient - self.defaultValue = `default`?.cs_toImportableNativeType() + self.defaultValue = { `default`()?.cs_toImportableNativeType() } self.versionHashModifier = versionHashModifier self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter @@ -290,45 +364,59 @@ public enum ValueContainer { get { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - return self.customGetter( - object, - { () -> V? in - - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) } - ) - } + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + let customGetter = (self.customGetter ?? { $1() }) + return customGetter( + object, + { () -> V? in + + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) } + ) + } + ) + } } set { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - CoreStore.assert( - object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." - ) - self.customSetter( - object, - { (newValue: V?) -> Void in - - object.rawObject!.setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { $0?.cs_toImportableNativeType() } - ) - }, - newValue + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + let customSetter = (self.customSetter ?? { $1($2) }) + customSetter( + object, + { (newValue: V?) -> Void in + + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { $0?.cs_toImportableNativeType() } + ) + }, + newValue + ) + } } } @@ -344,21 +432,81 @@ public enum ValueContainer { internal let isOptional = true internal let isIndexed: Bool internal let isTransient: Bool - internal let defaultValue: Any? internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let defaultValue: () -> Any? internal let affectedByKeyPaths: () -> Set + internal weak var parentObject: CoreStoreObject? - internal var parentObject: () -> CoreStoreObject = { + internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in - CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") + let keyPath = self.keyPath + guard let customGetter = self.customGetter else { + + guard let _ = self.customSetter else { + + return nil + } + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + return rawObject.getValue(forKvcKey: keyPath) + } + } + 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) } + ) + } + ) + return value?.cs_toImportableNativeType() + } + } + + 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 { (_ id: Any, _ newValue: Any?) -> Void in + + let rawObject = id as! CoreStoreManagedObject + customSetter( + O.cs_fromRaw(object: rawObject), + { (userValue: V?) -> Void in + + rawObject.setValue( + userValue, + forKvcKey: keyPath, + willSetValue: { $0?.cs_toImportableNativeType() } + ) + }, + (newValue as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) + ) + } } // MARK: Private - private let customGetter: (_ `self`: O, _ getValue: () -> V?) -> V? - private let customSetter: (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void + private let customGetter: ((_ `self`: O, _ getValue: () -> V?) -> V?)? + private let customSetter: ((_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void)? } } @@ -391,8 +539,8 @@ public extension ValueContainer.Required where V: EmptyableAttributeType { isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, - customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void = { $1($2) }, + customGetter: ((_ `self`: O, _ getValue: () -> V) -> V)? = nil, + customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.init( @@ -463,13 +611,13 @@ public enum TransformableContainer { */ public init( _ keyPath: KeyPath, - `default`: V, + `default`: @autoclosure @escaping () -> V, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, - customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void = { $1($2) }, + customGetter: ((_ `self`: O, _ getValue: () -> V) -> V)? = nil, + customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath @@ -490,44 +638,58 @@ public enum TransformableContainer { get { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - return self.customGetter( - object, - { () -> V in - - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { $0 as! V } - ) - } + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + let customGetter = (self.customGetter ?? { $1() }) + return customGetter( + object, + { () -> V in + + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { $0 as! V } + ) + } + ) + } } set { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - CoreStore.assert( - object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." - ) - self.customSetter( - object, - { (newValue: V) -> Void in - - object.rawObject!.setValue( - newValue, - forKvcKey: self.keyPath - ) - }, - newValue + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + let customSetter = (self.customSetter ?? { $1($2) }) + customSetter( + object, + { (newValue: V) -> Void in + + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath + ) + }, + newValue + ) + } } } @@ -544,21 +706,71 @@ public enum TransformableContainer { internal let isOptional = false internal let isIndexed: Bool internal let isTransient: Bool - internal let defaultValue: Any? internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let defaultValue: () -> Any? internal let affectedByKeyPaths: () -> Set + internal weak var parentObject: CoreStoreObject? - internal var parentObject: () -> CoreStoreObject = { + internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in - CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") + let keyPath = self.keyPath + guard let customGetter = self.customGetter else { + + guard let _ = self.customSetter else { + + return nil + } + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + return rawObject.getValue(forKvcKey: keyPath) + } + } + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + return customGetter( + O.cs_fromRaw(object: rawObject), + { rawObject.getValue(forKvcKey: keyPath) as! V } + ) + } + } + + 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 { (_ id: Any, _ newValue: Any?) -> Void in + + let rawObject = id as! CoreStoreManagedObject + customSetter( + O.cs_fromRaw(object: rawObject), + { (userValue: V) -> Void in + + rawObject.setValue(userValue, forKvcKey: keyPath) + }, + newValue as! V + ) + } } // MARK: Private - private let customGetter: (_ `self`: O, _ getValue: () -> V) -> V - private let customSetter: (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void + private let customGetter: ((_ `self`: O, _ getValue: () -> V) -> V)? + private let customSetter: ((_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void)? } @@ -601,13 +813,13 @@ public enum TransformableContainer { */ public init( _ keyPath: KeyPath, - `default`: V? = nil, + `default`: @autoclosure @escaping () -> V? = nil, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, - customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, - customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void = { $1($2) }, + customGetter: ((_ `self`: O, _ getValue: () -> V?) -> V?)? = nil, + customSetter: ((_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath @@ -628,44 +840,58 @@ public enum TransformableContainer { get { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - return self.customGetter( - object, - { () -> V? in - - return object.rawObject!.getValue( - forKvcKey: self.keyPath, - didGetValue: { $0 as! V? } - ) - } + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + let customGetter = (self.customGetter ?? { $1() }) + return customGetter( + object, + { () -> V? in + + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { $0 as! V? } + ) + } + ) + } } set { - let object = self.parentObject() as! O CoreStore.assert( - object.rawObject!.isRunningInAllowedQueue() == true, - "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." - ) - CoreStore.assert( - object.rawObject!.isEditableInContext() == true, - "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." - ) - self.customSetter( - object, - { (newValue: V?) -> Void in - - object.rawObject!.setValue( - newValue, - forKvcKey: self.keyPath - ) - }, - newValue + self.parentObject != nil, + "Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." ) + return withExtendedLifetime(self.parentObject! as! O) { (object: O) in + + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + let customSetter = (self.customSetter ?? { $1($2) }) + customSetter( + object, + { (newValue: V?) -> Void in + + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath + ) + }, + newValue + ) + } } } @@ -682,21 +908,71 @@ public enum TransformableContainer { internal let isOptional = true internal let isIndexed: Bool internal let isTransient: Bool - internal let defaultValue: Any? internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let defaultValue: () -> Any? internal let affectedByKeyPaths: () -> Set + internal weak var parentObject: CoreStoreObject? - internal var parentObject: () -> CoreStoreObject = { + internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in - CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") + let keyPath = self.keyPath + guard let customGetter = self.customGetter else { + + guard let _ = self.customSetter else { + + return nil + } + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + return rawObject.getValue(forKvcKey: keyPath) + } + } + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + return customGetter( + O.cs_fromRaw(object: rawObject), + { rawObject.getValue(forKvcKey: keyPath) as! V? } + ) + } + } + + 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 { (_ id: Any, _ newValue: Any?) -> Void in + + let rawObject = id as! CoreStoreManagedObject + customSetter( + O.cs_fromRaw(object: rawObject), + { (userValue: V?) -> Void in + + rawObject.setValue(userValue, forKvcKey: keyPath) + }, + newValue as! V? + ) + } } // MARK: Private - private let customGetter: (_ `self`: O, _ getValue: () -> V?) -> V? - private let customSetter: (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void + private let customGetter: ((_ `self`: O, _ getValue: () -> V?) -> V?)? + private let customSetter: ((_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void)? } } @@ -999,9 +1275,11 @@ internal protocol AttributeProtocol: class { var isOptional: Bool { get } var isIndexed: Bool { get } var isTransient: Bool { get } - var defaultValue: Any? { get } var versionHashModifier: String? { get } var renamingIdentifier: String? { get } + var defaultValue: () -> Any? { get } var affectedByKeyPaths: () -> Set { get } - var parentObject: () -> CoreStoreObject { get set } + weak var parentObject: CoreStoreObject? { get set } + var getter: CoreStoreManagedObject.CustomGetter? { get } + var setter: CoreStoreManagedObject.CustomSetter? { get } }