diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index a34b673..0801a46 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -238,6 +238,10 @@ B538BA781D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; }; B538BA791D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; }; B538BA7A1D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; }; + B53B275F1EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; }; + B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; }; + B53B27611EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; }; + B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; }; B53FB9FE1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; B53FBA011CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; @@ -752,6 +756,7 @@ B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Logging.swift"; sourceTree = ""; }; B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+CoreStore.swift"; sourceTree = ""; }; B538BA701D15B3E30003A766 /* CoreStoreBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreStoreBridge.m; sourceTree = ""; }; + B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreManagedObject.swift; sourceTree = ""; }; B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSMigrationResult.swift; sourceTree = ""; }; B53FBA031CAB300C00F0D40A /* CSMigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSMigrationType.swift; sourceTree = ""; }; B53FBA0A1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSCoreStore+Migrating.swift"; sourceTree = ""; }; @@ -1425,16 +1430,17 @@ children = ( B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */, B526613F1CADD585007B85D9 /* CoreStoreFetchRequest+CoreStore.swift */, - B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */, - B56923C31EB823B4007C4DC9 /* NSEntityDescription+Migration.swift */, + B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */, + B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */, B51260921E9B28F100402229 /* EntityIdentifier.swift */, B54A6A541BA15F2A007870FD /* FetchedResultsControllerDelegate.swift */, B5E834BA1B7691F3001D3D50 /* Functions.swift */, B5FAD6AB1B51285300714891 /* MigrationManager.swift */, B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */, - B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */, - B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */, + B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */, + B56923C31EB823B4007C4DC9 /* NSEntityDescription+Migration.swift */, B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */, + B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */, B5E84F2C1AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift */, B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */, B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */, @@ -1875,6 +1881,7 @@ B5E2222A1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */, B56923E81EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */, + B53B275F1EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */, B5D33A011E96012400C880DE /* Relationship.swift in Sources */, B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */, B56923C91EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */, @@ -2059,6 +2066,7 @@ 82BA18A71C4BBD2900A0916E /* CoreStore+Logging.swift in Sources */, 82BA18D81C4BBD7100A0916E /* WeakObject.swift in Sources */, B56923E91EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */, + B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */, B5D33A021E96012400C880DE /* Relationship.swift in Sources */, B559CD4B1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, B56923CA1EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */, @@ -2243,6 +2251,7 @@ B52DD1A71BE1F93200949AFE /* BaseDataTransaction+Querying.swift in Sources */, B546F96C1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, B56923EB1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */, + B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */, B5D33A041E96012400C880DE /* Relationship.swift in Sources */, B52DD1C61BE1F94600949AFE /* NSManagedObjectContext+CoreStore.swift in Sources */, B56923CC1EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */, @@ -2427,6 +2436,7 @@ B563219F1BD65216006C9394 /* ObjectMonitor.swift in Sources */, B56321B61BD6521C006C9394 /* WeakObject.swift in Sources */, B56923EA1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */, + B53B27611EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */, B5D33A031E96012400C880DE /* Relationship.swift in Sources */, B559CD4C1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, B56923CB1EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */, diff --git a/Sources/CoreStoreManagedObject.swift b/Sources/CoreStoreManagedObject.swift new file mode 100644 index 0000000..f3c3b6c --- /dev/null +++ b/Sources/CoreStoreManagedObject.swift @@ -0,0 +1,48 @@ +// +// CoreStoreManagedObject.swift +// CoreStore +// +// Created by John Rommel Estropia on 2017/06/04. +// Copyright © 2017 John Rommel Estropia. All rights reserved. +// + +import CoreData + + +// MARK: - CoreStoreManagedObject + +@objc internal class CoreStoreManagedObject: NSManagedObject { + + @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) + } + } +} + + +// MARK: - Private + +private enum Static { + + static let queue = DispatchQueue.concurrent("com.coreStore.coreStoreManagerObjectBarrierQueue") + static var cache: [ObjectIdentifier: [KeyPath: Set]] = [:] +} diff --git a/Sources/CoreStoreSchema.swift b/Sources/CoreStoreSchema.swift index 5886ec4..7fa7292 100644 --- a/Sources/CoreStoreSchema.swift +++ b/Sources/CoreStoreSchema.swift @@ -200,35 +200,39 @@ public final class CoreStoreSchema: DynamicSchema { public func rawModel() -> NSManagedObjectModel { - if let cachedRawModel = self.cachedRawModel { + return CoreStoreSchema.barrierQueue.sync(flags: .barrier) { - return cachedRawModel - } - let rawModel = NSManagedObjectModel() - var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:] - for entity in self.allEntities { + if let cachedRawModel = self.cachedRawModel { + + return cachedRawModel + } + let rawModel = NSManagedObjectModel() + var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:] + for entity in self.allEntities { + + let entityDescription = self.entityDescription( + for: entity, + initializer: CoreStoreSchema.firstPassCreateEntityDescription + ) + entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription) + } + CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity) + CoreStoreSchema.thirdPassConnectInheritanceTree(for: entityDescriptionsByEntity) + CoreStoreSchema.fourthPassSynthesizeManagedObjectClasses(for: entityDescriptionsByEntity) - let entityDescription = self.entityDescription( - for: entity, - initializer: CoreStoreSchema.firstPassCreateEntityDescription - ) - entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription) + rawModel.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! }) + for (configuration, entities) in self.entitiesByConfiguration { + + rawModel.setEntities( + entities + .map({ entityDescriptionsByEntity[$0]! }) + .sorted(by: { $0.name! < $1.name! }), + forConfigurationName: configuration + ) + } + self.cachedRawModel = rawModel + return rawModel } - CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity) - CoreStoreSchema.thirdPassConnectInheritanceTree(for: entityDescriptionsByEntity) - - rawModel.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! }) - for (configuration, entities) in self.entitiesByConfiguration { - - rawModel.setEntities( - entities - .map({ entityDescriptionsByEntity[$0]! }) - .sorted(by: { $0.name! < $1.name! }), - forConfigurationName: configuration - ) - } - self.cachedRawModel = rawModel - return rawModel } @@ -264,8 +268,9 @@ public final class CoreStoreSchema: DynamicSchema { entityDescription.name = entity.entityName entityDescription.isAbstract = entity.isAbstract entityDescription.versionHashModifier = entity.versionHashModifier - entityDescription.managedObjectClassName = NSStringFromClass(NSManagedObject.self) + entityDescription.managedObjectClassName = "\(NSStringFromClass(entity.type)).CoreStoreManagedObject" + var keyPathsByAffectedKeyPaths: [KeyPath: Set] = [:] func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] { var propertyDescriptions: [NSPropertyDescription] = [] @@ -284,6 +289,7 @@ public final class CoreStoreSchema: DynamicSchema { description.versionHashModifier = attribute.versionHashModifier description.renamingIdentifier = attribute.renamingIdentifier propertyDescriptions.append(description) + keyPathsByAffectedKeyPaths[attribute.keyPath] = attribute.affectedByKeyPaths() case let relationship as RelationshipProtocol: let description = NSRelationshipDescription() @@ -295,6 +301,7 @@ public final class CoreStoreSchema: DynamicSchema { description.versionHashModifier = relationship.versionHashModifier description.renamingIdentifier = relationship.renamingIdentifier propertyDescriptions.append(description) + keyPathsByAffectedKeyPaths[relationship.keyPath] = relationship.affectedByKeyPaths() default: continue @@ -302,7 +309,7 @@ public final class CoreStoreSchema: DynamicSchema { } return propertyDescriptions } - + entityDescription.keyPathsByAffectedKeyPaths = keyPathsByAffectedKeyPaths entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type) return entityDescription } @@ -425,4 +432,43 @@ public final class CoreStoreSchema: DynamicSchema { ) } } + + private static func fourthPassSynthesizeManagedObjectClasses(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) { + + func createManagedObjectSubclass(for entityDescription: NSEntityDescription) { + + let superEntity = entityDescription.superentity + let className = entityDescription.managedObjectClassName! + guard case nil = NSClassFromString(className) as! CoreStoreManagedObject.Type? else { + + return + } + if let superEntity = superEntity { + + createManagedObjectSubclass(for: superEntity) + } + let superClass = cs_lazy { () -> CoreStoreManagedObject.Type in + + if let superClassName = superEntity?.managedObjectClassName, + let superClass = NSClassFromString(superClassName) { + + return superClass as! CoreStoreManagedObject.Type + } + return CoreStoreManagedObject.self + } + let managedObjectClass = className.withCString { + + return objc_allocateClassPair(superClass, $0, 0) as! CoreStoreManagedObject.Type + } + objc_registerClassPair(managedObjectClass) + managedObjectClass.cs_setKeyPathsForValuesAffectingKeys( + entityDescription.keyPathsByAffectedKeyPaths, + for: managedObjectClass + ) + } + for (_, entityDescription) in entityDescriptionsByEntity { + + createManagedObjectSubclass(for: entityDescription) + } + } } diff --git a/Sources/NSEntityDescription+DynamicModel.swift b/Sources/NSEntityDescription+DynamicModel.swift index 045e2af..bcf7bd8 100644 --- a/Sources/NSEntityDescription+DynamicModel.swift +++ b/Sources/NSEntityDescription+DynamicModel.swift @@ -54,17 +54,43 @@ internal extension NSEntityDescription { if let newValue = newValue { - var userInfo: [AnyHashable : Any] = [ - UserInfoKey.CoreStoreManagedObjectTypeName: NSStringFromClass(newValue.type), - UserInfoKey.CoreStoreManagedObjectEntityName: newValue.entityName, - UserInfoKey.CoreStoreManagedObjectIsAbstract: newValue.isAbstract - ] - userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] = newValue.versionHashModifier - self.userInfo = userInfo + cs_setUserInfo { (userInfo) in + + userInfo[UserInfoKey.CoreStoreManagedObjectTypeName] = NSStringFromClass(newValue.type) + userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] = newValue.entityName + userInfo[UserInfoKey.CoreStoreManagedObjectIsAbstract] = newValue.isAbstract + userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] = newValue.versionHashModifier + } } else { - self.userInfo = [:] + cs_setUserInfo { (userInfo) in + + userInfo[UserInfoKey.CoreStoreManagedObjectTypeName] = nil + userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] = nil + userInfo[UserInfoKey.CoreStoreManagedObjectIsAbstract] = nil + userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] = nil + } + } + } + } + + internal var keyPathsByAffectedKeyPaths: [KeyPath: Set] { + + get { + + if let userInfo = self.userInfo, + let function = userInfo[UserInfoKey.CoreStoreManagedObjectKeyPathsByAffectedKeyPaths] as! [KeyPath: Set]? { + + return function + } + return [:] + } + set { + + cs_setUserInfo { (userInfo) in + + userInfo[UserInfoKey.CoreStoreManagedObjectKeyPathsByAffectedKeyPaths] = newValue } } } @@ -74,11 +100,20 @@ internal extension NSEntityDescription { // MARK: - UserInfoKey - fileprivate enum UserInfoKey { + private enum UserInfoKey { fileprivate static let CoreStoreManagedObjectTypeName = "CoreStoreManagedObjectTypeName" fileprivate static let CoreStoreManagedObjectEntityName = "CoreStoreManagedObjectEntityName" fileprivate static let CoreStoreManagedObjectIsAbstract = "CoreStoreManagedObjectIsAbstract" fileprivate static let CoreStoreManagedObjectVersionHashModifier = "CoreStoreManagedObjectVersionHashModifier" + + fileprivate static let CoreStoreManagedObjectKeyPathsByAffectedKeyPaths = "CoreStoreManagedObjectKeyPathsByAffectedKeyPaths" + } + + private func cs_setUserInfo(_ closure: (_ userInfo: inout [AnyHashable: Any]) -> Void) { + + var userInfo = self.userInfo ?? [:] + closure(&userInfo) + self.userInfo = userInfo } } diff --git a/Sources/Relationship.swift b/Sources/Relationship.swift index 1184bdd..dbfb708 100644 --- a/Sources/Relationship.swift +++ b/Sources/Relationship.swift @@ -92,10 +92,23 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + deleteRule: DeleteRule = .nullify, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + inverseKeyPath: { nil }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -113,10 +126,24 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + inverse: @escaping (D) -> RelationshipContainer.ToOne, + deleteRule: DeleteRule = .nullify, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -134,10 +161,24 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, + deleteRule: DeleteRule = .nullify, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -155,10 +196,24 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, + deleteRule: DeleteRule = .nullify, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -189,6 +244,7 @@ public enum RelationshipContainer { internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let affectedByKeyPaths: () -> Set internal var parentObject: () -> CoreStoreObject = { @@ -230,13 +286,14 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?) { + private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?, affectedByKeyPaths: @autoclosure @escaping () -> Set) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue self.inverse = (D.self, inverseKeyPath) self.versionHashModifier = versionHashModifier self.renamingIdentifier = renamingIdentifier + self.affectedByKeyPaths = affectedByKeyPaths } } @@ -273,10 +330,27 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, minCount: Int = 0, maxCount: Int = 0, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + minCount: Int = 0, + maxCount: Int = 0, + deleteRule: DeleteRule = .nullify, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, minCount: minCount, maxCount: maxCount, inverseKeyPath: { nil }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + minCount: minCount, + maxCount: maxCount, + inverseKeyPath: { nil }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -296,10 +370,28 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, minCount: Int = 0, maxCount: Int = 0, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + minCount: Int = 0, + maxCount: Int = 0, + inverse: @escaping (D) -> RelationshipContainer.ToOne, + deleteRule: DeleteRule = .nullify, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, minCount: minCount, maxCount: maxCount, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + minCount: minCount, + maxCount: maxCount, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -319,10 +411,28 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, minCount: Int = 0, maxCount: Int = 0, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + minCount: Int = 0, + maxCount: Int = 0, + inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, + deleteRule: DeleteRule = .nullify, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, minCount: minCount, maxCount: maxCount, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + minCount: minCount, + maxCount: maxCount, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -342,10 +452,28 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, minCount: Int = 0, maxCount: Int = 0, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + minCount: Int = 0, + maxCount: Int = 0, + inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, + deleteRule: DeleteRule = .nullify, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, minCount: minCount, maxCount: maxCount, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + minCount: minCount, + maxCount: maxCount, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -377,6 +505,7 @@ public enum RelationshipContainer { internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let affectedByKeyPaths: () -> Set internal var parentObject: () -> CoreStoreObject = { @@ -418,7 +547,7 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: String, minCount: Int, maxCount: Int, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?) { + private init(keyPath: String, minCount: Int, maxCount: Int, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?, affectedByKeyPaths: @autoclosure @escaping () -> Set) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue @@ -429,6 +558,7 @@ public enum RelationshipContainer { let range = (Swift.max(0, minCount) ... maxCount) self.minCount = range.lowerBound self.maxCount = range.upperBound + self.affectedByKeyPaths = affectedByKeyPaths } } @@ -466,10 +596,27 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + deleteRule: DeleteRule = .nullify, + minCount: Int = 0, + maxCount: Int = 0, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + inverseKeyPath: { nil }, + deleteRule: deleteRule, + minCount: minCount, + maxCount: maxCount, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -489,10 +636,28 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + inverse: @escaping (D) -> RelationshipContainer.ToOne, + deleteRule: DeleteRule = .nullify, + minCount: Int = 0, + maxCount: Int = 0, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + minCount: minCount, + maxCount: maxCount, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -512,10 +677,28 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, + deleteRule: DeleteRule = .nullify, + minCount: Int = 0, + maxCount: Int = 0, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + minCount: minCount, + maxCount: maxCount, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -535,10 +718,28 @@ public enum RelationshipContainer { - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { + public convenience init( + _ keyPath: KeyPath, + inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, + deleteRule: DeleteRule = .nullify, + minCount: Int = 0, + maxCount: Int = 0, + versionHashModifier: String? = nil, + renamingIdentifier: String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + minCount: minCount, + maxCount: maxCount, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + affectedByKeyPaths: affectedByKeyPaths + ) } /** @@ -570,6 +771,7 @@ public enum RelationshipContainer { internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let affectedByKeyPaths: () -> Set internal var parentObject: () -> CoreStoreObject = { @@ -611,7 +813,7 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?) { + private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?, affectedByKeyPaths: @autoclosure @escaping () -> Set) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue @@ -622,6 +824,7 @@ public enum RelationshipContainer { let range = (Swift.max(0, minCount) ... maxCount) self.minCount = range.lowerBound self.maxCount = range.upperBound + self.affectedByKeyPaths = affectedByKeyPaths } } @@ -988,6 +1191,7 @@ internal protocol RelationshipProtocol: class { var isOrdered: Bool { get } var deleteRule: NSDeleteRule { get } var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get } + var affectedByKeyPaths: () -> Set { get } var parentObject: () -> CoreStoreObject { get set } var versionHashModifier: String? { get } var renamingIdentifier: String? { get } diff --git a/Sources/Value.swift b/Sources/Value.swift index 5e7d7c4..17b9941 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -114,8 +114,18 @@ public enum ValueContainer { - parameter setValue: the original setter for the property - parameter finalNewValue: the transformed new value - parameter originalNewValue: the original new value + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public init(_ keyPath: KeyPath, `default`: 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) }) { + public init( + _ keyPath: KeyPath, + `default`: 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) }, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath self.isIndexed = isIndexed @@ -125,6 +135,7 @@ public enum ValueContainer { self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter self.customSetter = customSetter + self.affectedByKeyPaths = affectedByKeyPaths } /** @@ -192,6 +203,7 @@ public enum ValueContainer { internal let defaultValue: Any? internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let affectedByKeyPaths: () -> Set internal var parentObject: () -> CoreStoreObject = { @@ -247,8 +259,18 @@ public enum ValueContainer { - parameter setValue: the original setter for the property - parameter finalNewValue: the transformed new value - parameter originalNewValue: the original new value + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public init(_ keyPath: KeyPath, `default`: 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) }) { + public init( + _ keyPath: KeyPath, + `default`: 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) }, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath self.isIndexed = isIndexed @@ -258,6 +280,7 @@ public enum ValueContainer { self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter self.customSetter = customSetter + self.affectedByKeyPaths = affectedByKeyPaths } /** @@ -324,6 +347,7 @@ public enum ValueContainer { internal let defaultValue: Any? internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let affectedByKeyPaths: () -> Set internal var parentObject: () -> CoreStoreObject = { @@ -359,8 +383,17 @@ public extension ValueContainer.Required where V: EmptyableAttributeType { - parameter setValue: the original setter for the property - parameter finalNewValue: the transformed new value - parameter originalNewValue: the original new value + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public convenience init(_ keyPath: KeyPath, 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) }) { + public convenience init( + _ keyPath: KeyPath, + 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) }, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.init( keyPath, @@ -370,7 +403,8 @@ public extension ValueContainer.Required where V: EmptyableAttributeType { versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier, customGetter: customGetter, - customSetter: customSetter + customSetter: customSetter, + affectedByKeyPaths: affectedByKeyPaths ) } } @@ -425,8 +459,18 @@ public enum TransformableContainer { - parameter setValue: the original setter for the property - parameter finalNewValue: the transformed new value - parameter originalNewValue: the original new value + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public init(_ keyPath: KeyPath, `default`: 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) }) { + public init( + _ keyPath: KeyPath, + `default`: 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) }, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath self.defaultValue = `default` @@ -436,6 +480,7 @@ public enum TransformableContainer { self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter self.customSetter = customSetter + self.affectedByKeyPaths = affectedByKeyPaths } /** @@ -502,6 +547,7 @@ public enum TransformableContainer { internal let defaultValue: Any? internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let affectedByKeyPaths: () -> Set internal var parentObject: () -> CoreStoreObject = { @@ -551,8 +597,18 @@ public enum TransformableContainer { - parameter setValue: the original setter for the property - parameter finalNewValue: the transformed new value - parameter originalNewValue: the original new value + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. */ - public init(_ keyPath: KeyPath, `default`: 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) }) { + public init( + _ keyPath: KeyPath, + `default`: 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) }, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { self.keyPath = keyPath self.defaultValue = `default` @@ -562,6 +618,7 @@ public enum TransformableContainer { self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter self.customSetter = customSetter + self.affectedByKeyPaths = affectedByKeyPaths } /** @@ -628,6 +685,7 @@ public enum TransformableContainer { internal let defaultValue: Any? internal let versionHashModifier: String? internal let renamingIdentifier: String? + internal let affectedByKeyPaths: () -> Set internal var parentObject: () -> CoreStoreObject = { @@ -944,5 +1002,6 @@ internal protocol AttributeProtocol: class { var defaultValue: Any? { get } var versionHashModifier: String? { get } var renamingIdentifier: String? { get } + var affectedByKeyPaths: () -> Set { get } var parentObject: () -> CoreStoreObject { get set } }