diff --git a/CoreStore.podspec b/CoreStore.podspec index b843cad..cd228b3 100644 --- a/CoreStore.podspec +++ b/CoreStore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreStore" - s.version = "4.0.3" + s.version = "4.0.4" 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/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/README.md b/README.md index d097cff..6a5f188 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Unleashing the real power of Core Data with the elegance and safety of Swift

* **Swift 3.1:** iOS 8+ / macOS 10.10+ / watchOS 2.0+ / tvOS 9.0+ +* Beta support: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/prototype/Swift_3_2), [Swift 4.0](https://github.com/JohnEstropia/CoreStore/tree/prototype/Swift_4_0) Upgrading from CoreStore 3.x to 4.x? Check out the [new features](#features) and make sure to read the [Migration guide](#upgrading-from-3xx-to-4xx). diff --git a/Sources/CoreStoreManagedObject.swift b/Sources/CoreStoreManagedObject.swift index 76d174b..5a851fd 100644 --- a/Sources/CoreStoreManagedObject.swift +++ b/Sources/CoreStoreManagedObject.swift @@ -13,28 +13,14 @@ import CoreData @objc internal class CoreStoreManagedObject: NSManagedObject { - @nonobjc - internal class func cs_setKeyPathsForValuesAffectingKeys(_ keyPathsForValuesAffectingKeys: [RawKeyPath: Set], for managedObjectClass: CoreStoreManagedObject.Type) { - - Static.queue.sync(flags: .barrier) { - - Static.cache[ObjectIdentifier(managedObjectClass)] = keyPathsForValuesAffectingKeys - } - } + 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?) - // MARK: NSManagedObject - - override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set { + @nonobjc @inline(__always) + internal static func cs_subclassName(for entity: DynamicEntity, in modelVersion: ModelVersion) -> String { - return Static.queue.sync(flags: .barrier) { - - let cacheKey = ObjectIdentifier(self) - if let keyPathsForValuesAffectingKeys = Static.cache[cacheKey] { - - return keyPathsForValuesAffectingKeys[key] ?? [] - } - return super.keyPathsForValuesAffectingValue(forKey: key) - } + return "_\(NSStringFromClass(CoreStoreManagedObject.self))__\(modelVersion)__\(NSStringFromClass(entity.type))__\(entity.entityName)" } } 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 c041ecf..0e11a7a 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: [RawKeyPath: CoreStoreManagedObject.CustomGetterSetter]] = [:] for entity in self.allEntities { - let entityDescription = self.entityDescription( + let (entityDescription, customGetterSetterByKeyPaths) = self.entityDescription( for: entity, - initializer: CoreStoreSchema.firstPassCreateEntityDescription + 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,29 +253,33 @@ public final class CoreStoreSchema: DynamicSchema { private let allEntities: Set private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:] + private var customGettersSettersByEntity: [DynamicEntity: [RawKeyPath: CoreStoreManagedObject.CustomGetterSetter]] = [:] private weak var cachedRawModel: NSManagedObjectModel? - private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity) -> NSEntityDescription) -> NSEntityDescription { + private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity, ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [RawKeyPath: CoreStoreManagedObject.CustomGetterSetter])) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [RawKeyPath: CoreStoreManagedObject.CustomGetterSetter]) { if let cachedEntityDescription = self.entityDescriptionsByEntity[entity] { - return cachedEntityDescription + return (cachedEntityDescription, self.customGettersSettersByEntity[entity] ?? [:]) } - let entityDescription = withoutActuallyEscaping(initializer, do: { $0(entity) }) + let modelVersion = self.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) -> NSEntityDescription { + private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [RawKeyPath: CoreStoreManagedObject.CustomGetterSetter]) { let entityDescription = NSEntityDescription() entityDescription.coreStoreEntity = entity entityDescription.name = entity.entityName entityDescription.isAbstract = entity.isAbstract entityDescription.versionHashModifier = entity.versionHashModifier - entityDescription.managedObjectClassName = "\(NSStringFromClass(CoreStoreManagedObject.self)).\(NSStringFromClass(entity.type)).\(entity.entityName)" + entityDescription.managedObjectClassName = CoreStoreManagedObject.cs_subclassName(for: entity, in: modelVersion) var keyPathsByAffectedKeyPaths: [RawKeyPath: Set] = [:] + var customGetterSetterByKeyPaths: [RawKeyPath: CoreStoreManagedObject.CustomGetterSetter] = [:] func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] { var propertyDescriptions: [NSPropertyDescription] = [] @@ -284,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() @@ -309,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]) { @@ -433,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: [RawKeyPath: CoreStoreManagedObject.CustomGetterSetter]]) { - func createManagedObjectSubclass(for entityDescription: NSEntityDescription) { + func createManagedObjectSubclass(for entityDescription: NSEntityDescription, customGetterSetterByKeyPaths: [RawKeyPath: CoreStoreManagedObject.CustomGetterSetter]?) { let superEntity = entityDescription.superentity let className = entityDescription.managedObjectClassName! @@ -445,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 @@ -456,19 +469,85 @@ 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 { + + 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))") + } + } + 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))") + } + } + } + + 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/Info.plist b/Sources/Info.plist index 1611783..32fbf9d 100644 --- a/Sources/Info.plist +++ b/Sources/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 4.0.3 + 4.0.4 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/NSEntityDescription+DynamicModel.swift b/Sources/NSEntityDescription+DynamicModel.swift index f7a7510..a7bad50 100644 --- a/Sources/NSEntityDescription+DynamicModel.swift +++ b/Sources/NSEntityDescription+DynamicModel.swift @@ -75,14 +75,15 @@ internal extension NSEntityDescription { } } + @nonobjc internal var keyPathsByAffectedKeyPaths: [RawKeyPath: Set] { get { if let userInfo = self.userInfo, - let function = userInfo[UserInfoKey.CoreStoreManagedObjectKeyPathsByAffectedKeyPaths] as! [RawKeyPath: Set]? { + let value = userInfo[UserInfoKey.CoreStoreManagedObjectKeyPathsByAffectedKeyPaths] { - return function + return value as! [RawKeyPath: Set] } return [:] } @@ -95,6 +96,27 @@ internal extension NSEntityDescription { } } + @nonobjc + internal var customGetterSetterByKeyPaths: [RawKeyPath: CoreStoreManagedObject.CustomGetterSetter] { + + get { + + if let userInfo = self.userInfo, + let value = userInfo[UserInfoKey.CoreStoreManagedObjectCustomGetterSetterByKeyPaths] { + + return value as! [RawKeyPath: 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 f93ac48..59807dd 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: () -> RawKeyPath?) { 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 97fd6b2..6dc136c 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -118,19 +118,19 @@ public enum ValueContainer { */ public init( _ keyPath: RawKeyPath, - `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_toQueryableNativeType() + self.defaultValue = { `default`().cs_toQueryableNativeType() } 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_fromQueryableNativeType($0 as! V.QueryableNativeType)! } - ) - } + 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_fromQueryableNativeType($0 as! V.QueryableNativeType)! } + ) + } + ) + } } 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_toQueryableNativeType() } - ) - }, - 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( + self.isTransient || 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_toQueryableNativeType() } + ) + }, + newValue + ) + } } } @@ -200,21 +214,65 @@ 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.") + guard let customGetter = self.customGetter else { + + return nil + } + let keyPath = self.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_fromQueryableNativeType($0 as! V.QueryableNativeType!)! } + ) + } + ) + return value.cs_toQueryableNativeType() + } + } + + internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in + + guard let customSetter = self.customSetter else { + + return nil + } + let keyPath = self.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_toQueryableNativeType() } + ) + }, + V.cs_fromQueryableNativeType(newValue as! V.QueryableNativeType)! + ) + } } // 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 +321,19 @@ public enum ValueContainer { */ public init( _ keyPath: RawKeyPath, - `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_toQueryableNativeType() + self.defaultValue = { `default`()?.cs_toQueryableNativeType() } self.versionHashModifier = versionHashModifier self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter @@ -290,45 +348,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.QueryableNativeType?).flatMap(V.cs_fromQueryableNativeType) } - ) - } + 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.QueryableNativeType?).flatMap(V.cs_fromQueryableNativeType) } + ) + } + ) + } } 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_toQueryableNativeType() } - ) - }, - 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( + self.isTransient || 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_toQueryableNativeType() } + ) + }, + newValue + ) + } } } @@ -344,21 +416,65 @@ 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.") + guard let customGetter = self.customGetter else { + + return nil + } + let keyPath = self.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.QueryableNativeType?).flatMap(V.cs_fromQueryableNativeType) } + ) + } + ) + return value?.cs_toQueryableNativeType() + } + } + + internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in + + guard let customSetter = self.customSetter else { + + return nil + } + let keyPath = self.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_toQueryableNativeType() } + ) + }, + (newValue as! V.QueryableNativeType?).flatMap(V.cs_fromQueryableNativeType) + ) + } } // 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 +507,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 +579,13 @@ public enum TransformableContainer { */ public init( _ keyPath: RawKeyPath, - `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 +606,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( + self.isTransient || 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 +674,55 @@ 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.") + guard let customGetter = self.customGetter else { + + return nil + } + let keyPath = self.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 + + guard let customSetter = self.customSetter else { + + return nil + } + let keyPath = self.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 +765,13 @@ public enum TransformableContainer { */ public init( _ keyPath: RawKeyPath, - `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 +792,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( + self.isTransient || 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 +860,63 @@ 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.") + guard let customGetter = self.customGetter else { + + return nil + } + let keyPath = self.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 +1219,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 } }