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 }
}