From b1972b82f127cd4839ee4f9a4e521333345c56bf Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 18 Apr 2017 11:29:16 +0900 Subject: [PATCH 01/14] added way to store userInfo in DataStack and in transactions --- Sources/Setup/DataStack.swift | 39 +++++++++++++++++ .../Transactions/BaseDataTransaction.swift | 43 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index eb42b7a..cad1e2f 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -442,6 +442,43 @@ public final class DataStack: Equatable { } + // MARK: 3rd Party Utilities + + /** + Allow external libraries to store custom data in the `DataStack`. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + CoreStore.defaultStack[userInfoKey: &Static.myDataKey] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + - parameter userInfoKey: the key for custom data. Make sure this is a static pointer that will never be changed. + - returns: A custom data identified by `userInfoKey` + */ + public subscript(userInfoKey key: UnsafeRawPointer) -> Any? { + + get { + + self.userInfoLock.lock() + defer { + + self.userInfoLock.unlock() + } + return self.userInfo[key] + } + set { + + self.userInfoLock.lock() + defer { + + self.userInfoLock.unlock() + } + self.userInfo[key] = newValue + } + } + + // MARK: Equatable public static func == (lhs: DataStack, rhs: DataStack) -> Bool { @@ -571,6 +608,8 @@ public final class DataStack: Equatable { private var persistentStoresByFinalConfiguration = [String: NSPersistentStore]() private var finalConfigurationsByEntityIdentifier = [EntityIdentifier: Set]() + private var userInfo: [UnsafeRawPointer: Any] = [:] + private let userInfoLock = NSRecursiveLock() deinit { diff --git a/Sources/Transactions/BaseDataTransaction.swift b/Sources/Transactions/BaseDataTransaction.swift index baefdf6..9406e0c 100644 --- a/Sources/Transactions/BaseDataTransaction.swift +++ b/Sources/Transactions/BaseDataTransaction.swift @@ -438,6 +438,43 @@ public /*abstract*/ class BaseDataTransaction { } + // MARK: 3rd Party Utilities + + /** + Allow external libraries to store custom data in the transaction. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + transaction[userInfoKey: &Static.myDataKey] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + - parameter userInfoKey: the key for custom data. Make sure this is a static pointer that will never be changed. + - returns: A custom data identified by `userInfoKey` + */ + public subscript(userInfoKey key: UnsafeRawPointer) -> Any? { + + get { + + self.userInfoLock.lock() + defer { + + self.userInfoLock.unlock() + } + return self.userInfo[key] + } + set { + + self.userInfoLock.lock() + defer { + + self.userInfoLock.unlock() + } + self.userInfo[key] = newValue + } + } + + // MARK: Internal internal let context: NSManagedObjectContext @@ -476,4 +513,10 @@ public /*abstract*/ class BaseDataTransaction { return self.bypassesQueueing || self.transactionQueue.cs_isCurrentExecutionContext() } + + + // MARK: Private + + private var userInfo: [UnsafeRawPointer: Any] = [:] + private let userInfoLock = NSRecursiveLock() } From 94e6db669fe75caf5b349e3d41cf8875b33ac541 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 18 Apr 2017 12:02:39 +0900 Subject: [PATCH 02/14] added a way to lazily-initialize user info data --- Sources/Setup/DataStack.swift | 30 ++++++++++++++++++- .../Transactions/BaseDataTransaction.swift | 29 ++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index cad1e2f..d6bff57 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -454,7 +454,6 @@ public final class DataStack: Equatable { ``` - Important: Do not use this method to store thread-sensitive data. - parameter userInfoKey: the key for custom data. Make sure this is a static pointer that will never be changed. - - returns: A custom data identified by `userInfoKey` */ public subscript(userInfoKey key: UnsafeRawPointer) -> Any? { @@ -478,6 +477,35 @@ public final class DataStack: Equatable { } } + /** + Allow external libraries to store custom data in the `DataStack`. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + CoreStore.defaultStack[userInfoKey: &Static.myDataKey, lazyInit: { MyObject() }] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + - parameter userInfoKey: the key for custom data. Make sure this is a static pointer that will never be changed. + - parameter lazyInit: a closure to use to lazily-initialize the data + - returns: A custom data identified by `userInfoKey` + */ + public subscript(userInfoKey key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any? { + + self.userInfoLock.lock() + defer { + + self.userInfoLock.unlock() + } + if let value = self.userInfo[key] { + + return value + } + let value = closure() + self.userInfo[key] = value + return value + } + // MARK: Equatable diff --git a/Sources/Transactions/BaseDataTransaction.swift b/Sources/Transactions/BaseDataTransaction.swift index 9406e0c..03fa157 100644 --- a/Sources/Transactions/BaseDataTransaction.swift +++ b/Sources/Transactions/BaseDataTransaction.swift @@ -474,6 +474,35 @@ public /*abstract*/ class BaseDataTransaction { } } + /** + Allow external libraries to store custom data in the transaction. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + CoreStore.defaultStack[userInfoKey: &Static.myDataKey, lazyInit: { MyObject() }] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + - parameter userInfoKey: the key for custom data. Make sure this is a static pointer that will never be changed. + - parameter lazyInit: a closure to use to lazily-initialize the data + - returns: A custom data identified by `userInfoKey` + */ + public subscript(userInfoKey key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any? { + + self.userInfoLock.lock() + defer { + + self.userInfoLock.unlock() + } + if let value = self.userInfo[key] { + + return value + } + let value = closure() + self.userInfo[key] = value + return value + } + // MARK: Internal From 1938f0d9de190e7ccc4df5390c469ff0e3d005bf Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 18 Apr 2017 12:09:37 +0900 Subject: [PATCH 03/14] no need to be optional --- Sources/Setup/DataStack.swift | 2 +- Sources/Transactions/BaseDataTransaction.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index d6bff57..7df7412 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -490,7 +490,7 @@ public final class DataStack: Equatable { - parameter lazyInit: a closure to use to lazily-initialize the data - returns: A custom data identified by `userInfoKey` */ - public subscript(userInfoKey key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any? { + public subscript(userInfoKey key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any { self.userInfoLock.lock() defer { diff --git a/Sources/Transactions/BaseDataTransaction.swift b/Sources/Transactions/BaseDataTransaction.swift index 03fa157..6c6acf9 100644 --- a/Sources/Transactions/BaseDataTransaction.swift +++ b/Sources/Transactions/BaseDataTransaction.swift @@ -487,7 +487,7 @@ public /*abstract*/ class BaseDataTransaction { - parameter lazyInit: a closure to use to lazily-initialize the data - returns: A custom data identified by `userInfoKey` */ - public subscript(userInfoKey key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any? { + public subscript(userInfoKey key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any { self.userInfoLock.lock() defer { From b6bc7c2edfbfe23e0c02e56aa8f2db62a6617af1 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 18 Apr 2017 18:14:16 +0900 Subject: [PATCH 04/14] allow edit() calls to any DynamicObject --- Sources/Transactions/AsynchronousDataTransaction.swift | 4 ++-- Sources/Transactions/SynchronousDataTransaction.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Transactions/AsynchronousDataTransaction.swift b/Sources/Transactions/AsynchronousDataTransaction.swift index d8a21c7..a5d2961 100644 --- a/Sources/Transactions/AsynchronousDataTransaction.swift +++ b/Sources/Transactions/AsynchronousDataTransaction.swift @@ -104,7 +104,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { - parameter object: the `NSManagedObject` type to be edited - returns: an editable proxy for the specified `NSManagedObject`. */ - public override func edit(_ object: T?) -> T? { + public override func edit(_ object: T?) -> T? { CoreStore.assert( !self.isCommitted, @@ -121,7 +121,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { - parameter objectID: the `NSManagedObjectID` for the object to be edited - returns: an editable proxy for the specified `NSManagedObject`. */ - public override func edit(_ into: Into, _ objectID: NSManagedObjectID) -> T? { + public override func edit(_ into: Into, _ objectID: NSManagedObjectID) -> T? { CoreStore.assert( !self.isCommitted, diff --git a/Sources/Transactions/SynchronousDataTransaction.swift b/Sources/Transactions/SynchronousDataTransaction.swift index 81d4bce..75bc63f 100644 --- a/Sources/Transactions/SynchronousDataTransaction.swift +++ b/Sources/Transactions/SynchronousDataTransaction.swift @@ -71,7 +71,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction { - parameter object: the `NSManagedObject` type to be edited - returns: an editable proxy for the specified `NSManagedObject`. */ - public override func edit(_ object: T?) -> T? { + public override func edit(_ object: T?) -> T? { CoreStore.assert( !self.isCommitted, @@ -88,7 +88,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction { - parameter objectID: the `NSManagedObjectID` for the object to be edited - returns: an editable proxy for the specified `NSManagedObject`. */ - public override func edit(_ into: Into, _ objectID: NSManagedObjectID) -> T? { + public override func edit(_ into: Into, _ objectID: NSManagedObjectID) -> T? { CoreStore.assert( !self.isCommitted, From b0e2655bdfe5b5d81356b2411f59fc0de1fc0a11 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 19 Apr 2017 17:04:34 +0900 Subject: [PATCH 05/14] added a typealias for the object type contained in ListMonitor and ObjectMonitor --- .../BaseDataTransaction+Importing.swift | 20 +++++++++---------- Sources/Observing/ListMonitor.swift | 5 +++++ Sources/Observing/ObjectMonitor.swift | 5 +++++ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Sources/Importing/BaseDataTransaction+Importing.swift b/Sources/Importing/BaseDataTransaction+Importing.swift index d368f9c..d76abc4 100644 --- a/Sources/Importing/BaseDataTransaction+Importing.swift +++ b/Sources/Importing/BaseDataTransaction+Importing.swift @@ -39,9 +39,9 @@ public extension BaseDataTransaction { - throws: an `Error` thrown from any of the `ImportableObject` methods - returns: the created `ImportableObject` instance, or `nil` if the import was ignored */ - public func importObject( + public func importObject( _ into: Into, - source: T.ImportSource) throws -> T? where T: DynamicObject, T: ImportableObject { + source: T.ImportSource) throws -> T? { CoreStore.assert( self.isRunningInAllowedQueue(), @@ -69,9 +69,9 @@ public extension BaseDataTransaction { - parameter source: the object to import values from - throws: an `Error` thrown from any of the `ImportableObject` methods */ - public func importObject( + public func importObject( _ object: T, - source: T.ImportSource) throws where T: DynamicObject, T: ImportableObject { + source: T.ImportSource) throws { CoreStore.assert( self.isRunningInAllowedQueue(), @@ -97,9 +97,9 @@ public extension BaseDataTransaction { - throws: an `Error` thrown from any of the `ImportableObject` methods - returns: the array of created `ImportableObject` instances */ - public func importObjects( + public func importObjects( _ into: Into, - sourceArray: S) throws -> [T] where T: DynamicObject, T: ImportableObject, S.Iterator.Element == T.ImportSource { + sourceArray: S) throws -> [T] where S.Iterator.Element == T.ImportSource { CoreStore.assert( self.isRunningInAllowedQueue(), @@ -133,9 +133,9 @@ public extension BaseDataTransaction { - throws: an `Error` thrown from any of the `ImportableUniqueObject` methods - returns: the created/updated `ImportableUniqueObject` instance, or `nil` if the import was ignored */ - public func importUniqueObject( + public func importUniqueObject( _ into: Into, - source: T.ImportSource) throws -> T? where T: DynamicObject, T: ImportableUniqueObject { + source: T.ImportSource) throws -> T? { CoreStore.assert( self.isRunningInAllowedQueue(), @@ -185,10 +185,10 @@ public extension BaseDataTransaction { - throws: an `Error` thrown from any of the `ImportableUniqueObject` methods - returns: the array of created/updated `ImportableUniqueObject` instances */ - public func importUniqueObjects( + public func importUniqueObjects( _ into: Into, sourceArray: S, - preProcess: @escaping (_ mapping: [T.UniqueIDType: T.ImportSource]) throws -> [T.UniqueIDType: T.ImportSource] = { $0 }) throws -> [T] where T: DynamicObject, T: ImportableUniqueObject, S.Iterator.Element == T.ImportSource { + preProcess: @escaping (_ mapping: [T.UniqueIDType: T.ImportSource]) throws -> [T.UniqueIDType: T.ImportSource] = { $0 }) throws -> [T] where S.Iterator.Element == T.ImportSource { CoreStore.assert( self.isRunningInAllowedQueue(), diff --git a/Sources/Observing/ListMonitor.swift b/Sources/Observing/ListMonitor.swift index 4d5a53e..78cb88b 100644 --- a/Sources/Observing/ListMonitor.swift +++ b/Sources/Observing/ListMonitor.swift @@ -71,6 +71,11 @@ public final class ListMonitor: Hashable { // MARK: Public (Accessors) + /** + The type for the objects contained bye the `ListMonitor` + */ + public typealias ObjectType = T + /** Returns the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`. diff --git a/Sources/Observing/ObjectMonitor.swift b/Sources/Observing/ObjectMonitor.swift index 8ab8047..51f6f87 100644 --- a/Sources/Observing/ObjectMonitor.swift +++ b/Sources/Observing/ObjectMonitor.swift @@ -42,6 +42,11 @@ import CoreData @available(OSX 10.12, *) public final class ObjectMonitor: Equatable { + /** + The type for the object contained by the `ObjectMonitor` + */ + public typealias ObjectType = EntityType + /** Returns the `NSManagedObject` instance being observed, or `nil` if the object was already deleted. */ From fd14a182482602be1c574070a3aad406b23c1b98 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Thu, 20 Apr 2017 10:42:47 +0900 Subject: [PATCH 06/14] Unify generics usage in ListMonitor and ObjectMonitor --- Sources/ObjectiveC/CSListMonitor.swift | 2 +- Sources/ObjectiveC/CSObjectMonitor.swift | 2 +- Sources/Observing/CoreStore+Observing.swift | 18 ++-- Sources/Observing/DataStack+Observing.swift | 18 ++-- Sources/Observing/ListMonitor.swift | 96 +++++++++---------- Sources/Observing/ObjectMonitor.swift | 32 +++---- .../UnsafeDataTransaction+Observing.swift | 18 ++-- 7 files changed, 93 insertions(+), 93 deletions(-) diff --git a/Sources/ObjectiveC/CSListMonitor.swift b/Sources/ObjectiveC/CSListMonitor.swift index 071210f..6e109d6 100644 --- a/Sources/ObjectiveC/CSListMonitor.swift +++ b/Sources/ObjectiveC/CSListMonitor.swift @@ -546,7 +546,7 @@ public final class CSListMonitor: NSObject { // MARK: - ListMonitor @available(OSX 10.12, *) -extension ListMonitor where T: NSManagedObject { +extension ListMonitor where ListMonitor.ObjectType: NSManagedObject { // MARK: CoreStoreSwiftType diff --git a/Sources/ObjectiveC/CSObjectMonitor.swift b/Sources/ObjectiveC/CSObjectMonitor.swift index aca5a49..52e58dd 100644 --- a/Sources/ObjectiveC/CSObjectMonitor.swift +++ b/Sources/ObjectiveC/CSObjectMonitor.swift @@ -138,7 +138,7 @@ public final class CSObjectMonitor: NSObject { // MARK: - ObjectMonitor @available(OSX 10.12, *) -extension ObjectMonitor where EntityType: NSManagedObject { +extension ObjectMonitor where ObjectMonitor.ObjectType: NSManagedObject { // MARK: CoreStoreSwiftType diff --git a/Sources/Observing/CoreStore+Observing.swift b/Sources/Observing/CoreStore+Observing.swift index 86c5a14..0bc78be 100644 --- a/Sources/Observing/CoreStore+Observing.swift +++ b/Sources/Observing/CoreStore+Observing.swift @@ -38,7 +38,7 @@ public extension CoreStore { - parameter object: the `NSManagedObject` to observe changes from - returns: a `ObjectMonitor` that monitors changes to `object` */ - public static func monitorObject(_ object: T) -> ObjectMonitor { + public static func monitorObject(_ object: T) -> ObjectMonitor { return self.defaultStack.monitorObject(object) } @@ -50,7 +50,7 @@ public extension CoreStore { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public static func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { + public static func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { return self.defaultStack.monitorList(from, fetchClauses) } @@ -62,7 +62,7 @@ public extension CoreStore { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public static func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { + public static func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { return self.defaultStack.monitorList(from, fetchClauses) } @@ -74,7 +74,7 @@ public extension CoreStore { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public static func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { + public static func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) } @@ -86,7 +86,7 @@ public extension CoreStore { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public static func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { + public static func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) } @@ -99,7 +99,7 @@ public extension CoreStore { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public static func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { + public static func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses) } @@ -112,7 +112,7 @@ public extension CoreStore { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public static func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { + public static func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses) } @@ -125,7 +125,7 @@ public extension CoreStore { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public static func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { + public static func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) } @@ -138,7 +138,7 @@ public extension CoreStore { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public static func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { + public static func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) } diff --git a/Sources/Observing/DataStack+Observing.swift b/Sources/Observing/DataStack+Observing.swift index c408637..1070b42 100644 --- a/Sources/Observing/DataStack+Observing.swift +++ b/Sources/Observing/DataStack+Observing.swift @@ -38,7 +38,7 @@ public extension DataStack { - parameter object: the `NSManagedObject` to observe changes from - returns: a `ObjectMonitor` that monitors changes to `object` */ - public func monitorObject(_ object: T) -> ObjectMonitor { + public func monitorObject(_ object: T) -> ObjectMonitor { CoreStore.assert( Thread.isMainThread, @@ -54,7 +54,7 @@ public extension DataStack { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { + public func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { return self.monitorList(from, fetchClauses) } @@ -66,7 +66,7 @@ public extension DataStack { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { + public func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { CoreStore.assert( Thread.isMainThread, @@ -95,7 +95,7 @@ public extension DataStack { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { + public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) } @@ -107,7 +107,7 @@ public extension DataStack { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { + public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { CoreStore.assert( Thread.isMainThread, @@ -138,7 +138,7 @@ public extension DataStack { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { + public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { return self.monitorSectionedList(from, sectionBy, fetchClauses) } @@ -151,7 +151,7 @@ public extension DataStack { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { + public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { CoreStore.assert( Thread.isMainThread, @@ -182,7 +182,7 @@ public extension DataStack { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { + public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) } @@ -195,7 +195,7 @@ public extension DataStack { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { + public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { CoreStore.assert( Thread.isMainThread, diff --git a/Sources/Observing/ListMonitor.swift b/Sources/Observing/ListMonitor.swift index 78cb88b..54e9be1 100644 --- a/Sources/Observing/ListMonitor.swift +++ b/Sources/Observing/ListMonitor.swift @@ -67,14 +67,14 @@ import CoreData In the example above, both `person1` and `person2` will contain the object at section=2, index=3. */ @available(OSX 10.12, *) -public final class ListMonitor: Hashable { +public final class ListMonitor: Hashable { // MARK: Public (Accessors) /** The type for the objects contained bye the `ListMonitor` */ - public typealias ObjectType = T + public typealias ObjectType = D /** Returns the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`. @@ -82,7 +82,7 @@ public final class ListMonitor: Hashable { - parameter index: the index of the object. Using an index above the valid range will raise an exception. - returns: the `NSManagedObject` at the specified index */ - public subscript(index: Int) -> T { + public subscript(index: Int) -> ObjectType { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, @@ -90,7 +90,7 @@ public final class ListMonitor: Hashable { ) if self.isSectioned { - return T.cs_fromRaw(object: self.fetchedResultsController.fetchedObjects![index]) + return ObjectType.cs_fromRaw(object: self.fetchedResultsController.fetchedObjects![index]) } return self[0, index] } @@ -101,14 +101,14 @@ public final class ListMonitor: Hashable { - parameter index: the index for the object. Using an index above the valid range will return `nil`. - returns: the `NSManagedObject` at the specified index, or `nil` if out of bounds */ - public subscript(safeIndex index: Int) -> T? { + public subscript(safeIndex index: Int) -> ObjectType? { if self.isSectioned { let fetchedObjects = self.fetchedResultsController.fetchedObjects! if index < fetchedObjects.count && index >= 0 { - return T.cs_fromRaw(object: fetchedObjects[index]) + return ObjectType.cs_fromRaw(object: fetchedObjects[index]) } return nil } @@ -122,7 +122,7 @@ public final class ListMonitor: Hashable { - parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will raise an exception. - returns: the `NSManagedObject` at the specified section and item index */ - public subscript(sectionIndex: Int, itemIndex: Int) -> T { + public subscript(sectionIndex: Int, itemIndex: Int) -> ObjectType { return self[NSIndexPath(indexes: [sectionIndex, itemIndex], length: 2) as IndexPath] } @@ -134,7 +134,7 @@ public final class ListMonitor: Hashable { - parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will return `nil`. - returns: the `NSManagedObject` at the specified section and item index, or `nil` if out of bounds */ - public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> T? { + public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> ObjectType? { guard let section = self.sectionInfoAtIndex(safeSectionIndex: sectionIndex) else { @@ -144,7 +144,7 @@ public final class ListMonitor: Hashable { return nil } - return T.cs_fromRaw(object: section.objects![itemIndex] as! NSManagedObject) + return ObjectType.cs_fromRaw(object: section.objects![itemIndex] as! NSManagedObject) } /** @@ -153,13 +153,13 @@ public final class ListMonitor: Hashable { - parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will raise an exception. - returns: the `NSManagedObject` at the specified index path */ - public subscript(indexPath: IndexPath) -> T { + public subscript(indexPath: IndexPath) -> ObjectType { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, "Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress." ) - return T.cs_fromRaw(object: self.fetchedResultsController.object(at: indexPath)) + return ObjectType.cs_fromRaw(object: self.fetchedResultsController.object(at: indexPath)) } /** @@ -168,7 +168,7 @@ public final class ListMonitor: Hashable { - parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will return `nil`. - returns: the `NSManagedObject` at the specified index path, or `nil` if out of bounds */ - public subscript(safeIndexPath indexPath: IndexPath) -> T? { + public subscript(safeIndexPath indexPath: IndexPath) -> ObjectType? { return self[ safeSectionIndex: indexPath[0], @@ -345,7 +345,7 @@ public final class ListMonitor: Hashable { - parameter object: the `NSManagedObject` to search the index of - returns: the index of the `NSManagedObject` if it exists in the `ListMonitor`'s fetched objects, or `nil` if not found. */ - public func indexOf(_ object: T) -> Int? { + public func indexOf(_ object: ObjectType) -> Int? { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, @@ -364,7 +364,7 @@ public final class ListMonitor: Hashable { - parameter object: the `NSManagedObject` to search the index of - returns: the `NSIndexPath` of the `NSManagedObject` if it exists in the `ListMonitor`'s fetched objects, or `nil` if not found. */ - public func indexPathOf(_ object: T) -> IndexPath? { + public func indexPathOf(_ object: ObjectType) -> IndexPath? { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, @@ -387,7 +387,7 @@ public final class ListMonitor: Hashable { - parameter observer: a `ListObserver` to send change notifications to */ - public func addObserver(_ observer: U) where U.ListEntityType == T { + public func addObserver(_ observer: U) where U.ListEntityType == ObjectType { self.unregisterObserver(observer) self.registerObserver( @@ -422,7 +422,7 @@ public final class ListMonitor: Hashable { - parameter observer: a `ListObjectObserver` to send change notifications to */ - public func addObserver(_ observer: U) where U.ListEntityType == T { + public func addObserver(_ observer: U) where U.ListEntityType == ObjectType { self.unregisterObserver(observer) self.registerObserver( @@ -476,7 +476,7 @@ public final class ListMonitor: Hashable { - parameter observer: a `ListSectionObserver` to send change notifications to */ - public func addObserver(_ observer: U) where U.ListEntityType == T { + public func addObserver(_ observer: U) where U.ListEntityType == ObjectType { self.unregisterObserver(observer) self.registerObserver( @@ -537,7 +537,7 @@ public final class ListMonitor: Hashable { - parameter observer: a `ListObserver` to unregister notifications to */ - public func removeObserver(_ observer: U) where U.ListEntityType == T { + public func removeObserver(_ observer: U) where U.ListEntityType == ObjectType { self.unregisterObserver(observer) } @@ -580,7 +580,7 @@ public final class ListMonitor: Hashable { // MARK: Equatable - public static func == (lhs: ListMonitor, rhs: ListMonitor) -> Bool { + public static func == (lhs: ListMonitor, rhs: ListMonitor) -> Bool { return lhs.fetchedResultsController === rhs.fetchedResultsController } @@ -590,7 +590,7 @@ public final class ListMonitor: Hashable { return lhs.fetchedResultsController === rhs.fetchedResultsController } - public static func ~= (lhs: ListMonitor, rhs: ListMonitor) -> Bool { + public static func ~= (lhs: ListMonitor, rhs: ListMonitor) -> Bool { return lhs.fetchedResultsController === rhs.fetchedResultsController } @@ -611,7 +611,7 @@ public final class ListMonitor: Hashable { // MARK: Internal - internal convenience init(dataStack: DataStack, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void) { + internal convenience init(dataStack: DataStack, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void) { self.init( context: dataStack.mainContext, @@ -623,7 +623,7 @@ public final class ListMonitor: Hashable { ) } - internal convenience init(dataStack: DataStack, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: @escaping (ListMonitor) -> Void) { + internal convenience init(dataStack: DataStack, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: @escaping (ListMonitor) -> Void) { self.init( context: dataStack.mainContext, @@ -635,7 +635,7 @@ public final class ListMonitor: Hashable { ) } - internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void) { + internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void) { self.init( context: unsafeTransaction.context, @@ -647,7 +647,7 @@ public final class ListMonitor: Hashable { ) } - internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: @escaping (ListMonitor) -> Void) { + internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: @escaping (ListMonitor) -> Void) { self.init( context: unsafeTransaction.context, @@ -659,7 +659,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor) -> Void) { + internal func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor) -> Void) { cs_setAssociatedRetainedObject( NotificationObserver( @@ -679,7 +679,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor, _ object: T, _ indexPath: IndexPath?, _ newIndexPath: IndexPath?) -> Void) { + internal func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor, _ object: ObjectType, _ indexPath: IndexPath?, _ newIndexPath: IndexPath?) -> Void) { cs_setAssociatedRetainedObject( NotificationObserver( @@ -689,7 +689,7 @@ public final class ListMonitor: Hashable { guard let `self` = self, let userInfo = note.userInfo, - let object = userInfo[String(describing: NSManagedObject.self)] as? T else { + let object = userInfo[String(describing: NSManagedObject.self)] as? ObjectType else { return } @@ -706,7 +706,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerSectionNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ sectionIndex: Int) -> Void) { + internal func registerSectionNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ sectionIndex: Int) -> Void) { cs_setAssociatedRetainedObject( NotificationObserver( @@ -729,7 +729,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerObserver(_ observer: U, willChange: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, didChange: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, willRefetch: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, didRefetch: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void) { + internal func registerObserver(_ observer: U, willChange: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, didChange: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, willRefetch: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, didRefetch: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void) { CoreStore.assert( Thread.isMainThread, @@ -789,7 +789,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerObserver(_ observer: U, didInsertObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: T, _ toIndexPath: IndexPath) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: T, _ fromIndexPath: IndexPath) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: T, _ atIndexPath: IndexPath) -> Void, didMoveObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: T, _ fromIndexPath: IndexPath, _ toIndexPath: IndexPath) -> Void) { + internal func registerObserver(_ observer: U, didInsertObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: ObjectType, _ toIndexPath: IndexPath) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: ObjectType, _ fromIndexPath: IndexPath) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: ObjectType, _ atIndexPath: IndexPath) -> Void, didMoveObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: ObjectType, _ fromIndexPath: IndexPath, _ toIndexPath: IndexPath) -> Void) { CoreStore.assert( Thread.isMainThread, @@ -850,7 +850,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerObserver(_ observer: U, didInsertSection: @escaping (_ observer: U, _ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ toIndex: Int) -> Void, didDeleteSection: @escaping (_ observer: U, _ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ fromIndex: Int) -> Void) { + internal func registerObserver(_ observer: U, didInsertSection: @escaping (_ observer: U, _ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ toIndex: Int) -> Void, didDeleteSection: @escaping (_ observer: U, _ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ fromIndex: Int) -> Void) { CoreStore.assert( Thread.isMainThread, @@ -1017,7 +1017,7 @@ public final class ListMonitor: Hashable { } } - private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: ((ListMonitor) -> Void)?) { + private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: ((ListMonitor) -> Void)?) { self.isSectioned = (sectionBy != nil) @@ -1129,23 +1129,23 @@ public final class ListMonitor: Hashable { } -// MARK: - ListMonitor where T: NSManagedObject +// MARK: - ListMonitor where ListMonitor.ObjectType: NSManagedObject @available(OSX 10.12, *) -extension ListMonitor where T: NSManagedObject { +extension ListMonitor where ListMonitor.ObjectType: NSManagedObject { /** Returns all objects in all sections - returns: all objects in all sections */ - public func objectsInAllSections() -> [T] { + public func objectsInAllSections() -> [ObjectType] { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, "Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress." ) - return (self.fetchedResultsController.dynamicCast() as NSFetchedResultsController).fetchedObjects ?? [] + return (self.fetchedResultsController.dynamicCast() as NSFetchedResultsController).fetchedObjects ?? [] } /** @@ -1154,9 +1154,9 @@ extension ListMonitor where T: NSManagedObject { - parameter section: the section index. Using an index outside the valid range will raise an exception. - returns: all objects in the specified section */ - public func objectsInSection(_ section: Int) -> [T] { + public func objectsInSection(_ section: Int) -> [ObjectType] { - return (self.sectionInfoAtIndex(section).objects as! [T]?) ?? [] + return (self.sectionInfoAtIndex(section).objects as! [ObjectType]?) ?? [] } /** @@ -1165,31 +1165,31 @@ extension ListMonitor where T: NSManagedObject { - parameter section: the section index. Using an index outside the valid range will return `nil`. - returns: all objects in the specified section */ - public func objectsInSection(safeSectionIndex section: Int) -> [T]? { + public func objectsInSection(safeSectionIndex section: Int) -> [ObjectType]? { - return self.sectionInfoAtIndex(safeSectionIndex: section)?.objects as! [T]? + return self.sectionInfoAtIndex(safeSectionIndex: section)?.objects as! [ObjectType]? } } -// MARK: - ListMonitor where T: CoreStoreObject +// MARK: - ListMonitor where ListMonitor.ObjectType: CoreStoreObject @available(OSX 10.12, *) -extension ListMonitor where T: CoreStoreObject { +extension ListMonitor where ListMonitor.ObjectType: CoreStoreObject { /** Returns all objects in all sections - returns: all objects in all sections */ - public func objectsInAllSections() -> [T] { + public func objectsInAllSections() -> [ObjectType] { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, "Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress." ) return (self.fetchedResultsController.fetchedObjects ?? []) - .map(T.cs_fromRaw) + .map(ObjectType.cs_fromRaw) } /** @@ -1198,10 +1198,10 @@ extension ListMonitor where T: CoreStoreObject { - parameter section: the section index. Using an index outside the valid range will raise an exception. - returns: all objects in the specified section */ - public func objectsInSection(_ section: Int) -> [T] { + public func objectsInSection(_ section: Int) -> [ObjectType] { return (self.sectionInfoAtIndex(section).objects ?? []) - .map({ T.cs_fromRaw(object: $0 as! NSManagedObject) }) + .map({ ObjectType.cs_fromRaw(object: $0 as! NSManagedObject) }) } /** @@ -1210,10 +1210,10 @@ extension ListMonitor where T: CoreStoreObject { - parameter section: the section index. Using an index outside the valid range will return `nil`. - returns: all objects in the specified section */ - public func objectsInSection(safeSectionIndex section: Int) -> [T]? { + public func objectsInSection(safeSectionIndex section: Int) -> [ObjectType]? { return (self.sectionInfoAtIndex(safeSectionIndex: section)?.objects)? - .map({ T.cs_fromRaw(object: $0 as! NSManagedObject) }) + .map({ ObjectType.cs_fromRaw(object: $0 as! NSManagedObject) }) } } diff --git a/Sources/Observing/ObjectMonitor.swift b/Sources/Observing/ObjectMonitor.swift index 51f6f87..3550a57 100644 --- a/Sources/Observing/ObjectMonitor.swift +++ b/Sources/Observing/ObjectMonitor.swift @@ -40,22 +40,22 @@ import CoreData Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles. */ @available(OSX 10.12, *) -public final class ObjectMonitor: Equatable { +public final class ObjectMonitor: Equatable { /** The type for the object contained by the `ObjectMonitor` */ - public typealias ObjectType = EntityType + public typealias ObjectType = D /** Returns the `NSManagedObject` instance being observed, or `nil` if the object was already deleted. */ - public var object: EntityType? { + public var object: ObjectType? { return self.fetchedResultsController .fetchedObjects? .first - .flatMap({ EntityType.cs_fromRaw(object: $0) }) + .flatMap({ ObjectType.cs_fromRaw(object: $0) }) } /** @@ -77,7 +77,7 @@ public final class ObjectMonitor: Equatable { - parameter observer: an `ObjectObserver` to send change notifications to */ - public func addObserver(_ observer: U) where U.ObjectEntityType == EntityType { + public func addObserver(_ observer: U) where U.ObjectEntityType == ObjectType { self.unregisterObserver(observer) self.registerObserver( @@ -104,7 +104,7 @@ public final class ObjectMonitor: Equatable { - parameter observer: an `ObjectObserver` to unregister notifications to */ - public func removeObserver(_ observer: U) where U.ObjectEntityType == EntityType { + public func removeObserver(_ observer: U) where U.ObjectEntityType == ObjectType { self.unregisterObserver(observer) } @@ -112,7 +112,7 @@ public final class ObjectMonitor: Equatable { // MARK: Equatable - public static func == (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { + public static func == (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { return lhs === rhs } @@ -122,7 +122,7 @@ public final class ObjectMonitor: Equatable { return lhs.fetchedResultsController === rhs.fetchedResultsController } - public static func ~= (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { + public static func ~= (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { return lhs === rhs } @@ -143,17 +143,17 @@ public final class ObjectMonitor: Equatable { // MARK: Internal - internal convenience init(dataStack: DataStack, object: EntityType) { + internal convenience init(dataStack: DataStack, object: ObjectType) { self.init(context: dataStack.mainContext, object: object) } - internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: EntityType) { + internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: ObjectType) { self.init(context: unsafeTransaction.context, object: object) } - internal func registerObserver(_ observer: U, willChangeObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: EntityType) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: EntityType) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: EntityType, _ changedPersistentKeys: Set) -> Void) { + internal func registerObserver(_ observer: U, willChangeObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: ObjectType) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: ObjectType) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: ObjectType, _ changedPersistentKeys: Set) -> Void) { CoreStore.assert( Thread.isMainThread, @@ -243,7 +243,7 @@ public final class ObjectMonitor: Equatable { private var didDeleteObjectKey: Void? private var didUpdateObjectKey: Void? - private init(context: NSManagedObjectContext, object: EntityType) { + private init(context: NSManagedObjectContext, object: ObjectType) { let rawObject = object.cs_toRaw() let fetchRequest = CoreStoreFetchRequest() @@ -258,7 +258,7 @@ public final class ObjectMonitor: Equatable { let fetchedResultsController = CoreStoreFetchedResultsController( context: context, fetchRequest: fetchRequest.dynamicCast(), - from: nil as From?, + from: nil as From?, applyFetchClauses: Where("SELF", isEqualTo: objectID).applyToFetchRequest ) @@ -274,7 +274,7 @@ public final class ObjectMonitor: Equatable { self.lastCommittedAttributes = (self.object?.cs_toRaw().committedValues(forKeys: nil) as? [String: NSObject]) ?? [:] } - private func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor) -> Void) { + private func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor) -> Void) { cs_setAssociatedRetainedObject( NotificationObserver( @@ -294,7 +294,7 @@ public final class ObjectMonitor: Equatable { ) } - private func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor, _ object: EntityType) -> Void) { + private func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor, _ object: ObjectType) -> Void) { cs_setAssociatedRetainedObject( NotificationObserver( @@ -308,7 +308,7 @@ public final class ObjectMonitor: Equatable { return } - callback(self, EntityType.cs_fromRaw(object: object)) + callback(self, ObjectType.cs_fromRaw(object: object)) } ), forKey: notificationKey, diff --git a/Sources/Observing/UnsafeDataTransaction+Observing.swift b/Sources/Observing/UnsafeDataTransaction+Observing.swift index 7e7ac14..e8ac0b7 100644 --- a/Sources/Observing/UnsafeDataTransaction+Observing.swift +++ b/Sources/Observing/UnsafeDataTransaction+Observing.swift @@ -38,7 +38,7 @@ public extension UnsafeDataTransaction { - parameter object: the `NSManagedObject` to observe changes from - returns: a `ObjectMonitor` that monitors changes to `object` */ - public func monitorObject(_ object: T) -> ObjectMonitor { + public func monitorObject(_ object: T) -> ObjectMonitor { return ObjectMonitor( unsafeTransaction: self, @@ -53,7 +53,7 @@ public extension UnsafeDataTransaction { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { + public func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { return self.monitorList(from, fetchClauses) } @@ -65,7 +65,7 @@ public extension UnsafeDataTransaction { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { + public func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { CoreStore.assert( fetchClauses.filter { $0 is OrderBy }.count > 0, @@ -90,7 +90,7 @@ public extension UnsafeDataTransaction { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { + public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) } @@ -102,7 +102,7 @@ public extension UnsafeDataTransaction { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { + public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { CoreStore.assert( fetchClauses.filter { $0 is OrderBy }.count > 0, @@ -129,7 +129,7 @@ public extension UnsafeDataTransaction { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { + public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { return self.monitorSectionedList(from, sectionBy, fetchClauses) } @@ -142,7 +142,7 @@ public extension UnsafeDataTransaction { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { + public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { CoreStore.assert( fetchClauses.filter { $0 is OrderBy }.count > 0, @@ -168,7 +168,7 @@ public extension UnsafeDataTransaction { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { + public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) } @@ -181,7 +181,7 @@ public extension UnsafeDataTransaction { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { + public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { CoreStore.assert( fetchClauses.filter { $0 is OrderBy }.count > 0, From a543a4c94ab64a63c7e9e66020955c516f2aa909 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Thu, 20 Apr 2017 16:40:27 +0900 Subject: [PATCH 07/14] Added a "userInfo" property to relevant types to allow external code to store custom data --- CoreStore.xcodeproj/project.pbxproj | 10 ++ Sources/Convenience/UserInfo.swift | 115 ++++++++++++++++++ Sources/Observing/ListMonitor.swift | 15 +++ Sources/Observing/ObjectMonitor.swift | 15 +++ Sources/Setup/DataStack.swift | 56 +-------- .../Transactions/BaseDataTransaction.swift | 63 +--------- 6 files changed, 160 insertions(+), 114 deletions(-) create mode 100644 Sources/Convenience/UserInfo.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 7df750f..8be28c6 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -434,6 +434,10 @@ B5A991ED1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */; }; B5A991EE1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */; }; B5A991EF1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */; }; + B5A9921F1EA898710091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; }; + B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; }; + B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; }; + B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; }; B5AEFAB51C9962AE00AD137F /* CoreStoreBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */; }; B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */; }; B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */; }; @@ -757,6 +761,7 @@ B5A261201B64BFDB006EB6D3 /* MigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationType.swift; sourceTree = ""; }; B5A5F2651CAEC50F004AB9AF /* CSSelect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSelect.swift; sourceTree = ""; }; B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionLock.swift; sourceTree = ""; }; + B5A9921E1EA898710091A2E3 /* UserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfo.swift; sourceTree = ""; }; B5AD60CD1C90141E00F2B2E8 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; }; B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreBridge.swift; sourceTree = ""; }; B5BDC91A1C202269008147CD /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Cartfile; path = ../Cartfile; sourceTree = ""; }; @@ -1323,6 +1328,7 @@ B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */, B5FAD6A81B50A4B300714891 /* Progress+Convenience.swift */, B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */, + B5A9921E1EA898710091A2E3 /* UserInfo.swift */, ); path = Convenience; sourceTree = ""; @@ -1692,6 +1698,7 @@ B52FD3AA1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, B52F74411E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, B51FE5AB1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, + B5A9921F1EA898710091A2E3 /* UserInfo.swift in Sources */, B54A6A551BA15F2A007870FD /* FetchedResultsControllerDelegate.swift in Sources */, B5D339E21E948C3600C880DE /* Value.swift in Sources */, B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */, @@ -1864,6 +1871,7 @@ B52FD3AB1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, B52F74421E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, B51FE5AD1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, + B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */, B5FE4DAD1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, B5D339E31E948C3600C880DE /* Value.swift in Sources */, 82BA18C51C4BBD5300A0916E /* ListObserver.swift in Sources */, @@ -2036,6 +2044,7 @@ B52FD3AD1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, B52F74441E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, B5ECDC2D1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, + B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */, B5D7A5BA1CA3BF8F005C752B /* CSInto.swift in Sources */, B5D339E51E948C3600C880DE /* Value.swift in Sources */, B5A5F26A1CAEC50F004AB9AF /* CSSelect.swift in Sources */, @@ -2208,6 +2217,7 @@ B52FD3AC1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, B52F74431E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, B51FE5AE1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, + B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */, B563218C1BD65216006C9394 /* DataStack+Transaction.swift in Sources */, B5D339E41E948C3600C880DE /* Value.swift in Sources */, B53FBA0E1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */, diff --git a/Sources/Convenience/UserInfo.swift b/Sources/Convenience/UserInfo.swift new file mode 100644 index 0000000..389c7cf --- /dev/null +++ b/Sources/Convenience/UserInfo.swift @@ -0,0 +1,115 @@ +// +// UserInfo.swift +// CoreStore +// +// Copyright © 2017 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + + +// MARK: UserInfo + +/** + The `UserInfo` class is provided by several CoreStore types such as `DataStack`, `ListMonitor`, `ObjectMonitor` and transactions to allow external libraries or user apps to store their own custom data. + ``` + enum Static { + static var myDataKey: Void? + } + CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject + ``` + - Important: Do not use this class to store thread-sensitive data. + */ +public final class UserInfo { + + /** + Allows external libraries to store custom data. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + - parameter key: the key for custom data. Make sure this is a static pointer that will never be changed. + */ + public subscript(key: UnsafeRawPointer) -> Any? { + + get { + + self.lock.lock() + defer { + + self.lock.unlock() + } + return self.data[key] + } + set { + + self.lock.lock() + defer { + + self.lock.unlock() + } + self.data[key] = newValue + } + } + + /** + Allows external libraries to store custom data in the `DataStack`. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + CoreStore.defaultStack.userInfo[&Static.myDataKey, lazyInit: { MyObject() }] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + - parameter key: the key for custom data. Make sure this is a static pointer that will never be changed. + - parameter lazyInit: a closure to use to lazily-initialize the data + - returns: A custom data identified by `key` + */ + public subscript(key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any { + + self.lock.lock() + defer { + + self.lock.unlock() + } + if let value = self.data[key] { + + return value + } + let value = closure() + self.data[key] = value + return value + } + + + // MARK: Internal + + internal init() {} + + + // MARK: Private + + private var data: [UnsafeRawPointer: Any] = [:] + private let lock = NSRecursiveLock() +} diff --git a/Sources/Observing/ListMonitor.swift b/Sources/Observing/ListMonitor.swift index 54e9be1..505c5fe 100644 --- a/Sources/Observing/ListMonitor.swift +++ b/Sources/Observing/ListMonitor.swift @@ -578,6 +578,21 @@ public final class ListMonitor: Hashable { } + // MARK: Public (3rd Party Utilities) + + /** + Allow external libraries to store custom data in the `ListMonitor`. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + monitor.userInfo[&Static.myDataKey] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + */ + private let userInfo = UserInfo() + + // MARK: Equatable public static func == (lhs: ListMonitor, rhs: ListMonitor) -> Bool { diff --git a/Sources/Observing/ObjectMonitor.swift b/Sources/Observing/ObjectMonitor.swift index 3550a57..85cbb1b 100644 --- a/Sources/Observing/ObjectMonitor.swift +++ b/Sources/Observing/ObjectMonitor.swift @@ -110,6 +110,21 @@ public final class ObjectMonitor: Equatable { } + // MARK: Public (3rd Party Utilities) + + /** + Allow external libraries to store custom data in the `ObjectMonitor`. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + monitor.userInfo[&Static.myDataKey] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + */ + private let userInfo = UserInfo() + + // MARK: Equatable public static func == (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index 7df7412..f07c32b 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -450,61 +450,11 @@ public final class DataStack: Equatable { enum Static { static var myDataKey: Void? } - CoreStore.defaultStack[userInfoKey: &Static.myDataKey] = myObject + CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject ``` - Important: Do not use this method to store thread-sensitive data. - - parameter userInfoKey: the key for custom data. Make sure this is a static pointer that will never be changed. */ - public subscript(userInfoKey key: UnsafeRawPointer) -> Any? { - - get { - - self.userInfoLock.lock() - defer { - - self.userInfoLock.unlock() - } - return self.userInfo[key] - } - set { - - self.userInfoLock.lock() - defer { - - self.userInfoLock.unlock() - } - self.userInfo[key] = newValue - } - } - - /** - Allow external libraries to store custom data in the `DataStack`. App code should rarely have a need for this. - ``` - enum Static { - static var myDataKey: Void? - } - CoreStore.defaultStack[userInfoKey: &Static.myDataKey, lazyInit: { MyObject() }] = myObject - ``` - - Important: Do not use this method to store thread-sensitive data. - - parameter userInfoKey: the key for custom data. Make sure this is a static pointer that will never be changed. - - parameter lazyInit: a closure to use to lazily-initialize the data - - returns: A custom data identified by `userInfoKey` - */ - public subscript(userInfoKey key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any { - - self.userInfoLock.lock() - defer { - - self.userInfoLock.unlock() - } - if let value = self.userInfo[key] { - - return value - } - let value = closure() - self.userInfo[key] = value - return value - } + private let userInfo = UserInfo() // MARK: Equatable @@ -636,8 +586,6 @@ public final class DataStack: Equatable { private var persistentStoresByFinalConfiguration = [String: NSPersistentStore]() private var finalConfigurationsByEntityIdentifier = [EntityIdentifier: Set]() - private var userInfo: [UnsafeRawPointer: Any] = [:] - private let userInfoLock = NSRecursiveLock() deinit { diff --git a/Sources/Transactions/BaseDataTransaction.swift b/Sources/Transactions/BaseDataTransaction.swift index 6c6acf9..4a1a45b 100644 --- a/Sources/Transactions/BaseDataTransaction.swift +++ b/Sources/Transactions/BaseDataTransaction.swift @@ -444,64 +444,13 @@ public /*abstract*/ class BaseDataTransaction { Allow external libraries to store custom data in the transaction. App code should rarely have a need for this. ``` enum Static { - static var myDataKey: Void? + static var myDataKey: Void? } - transaction[userInfoKey: &Static.myDataKey] = myObject + transaction.userInfo[&Static.myDataKey] = myObject ``` - Important: Do not use this method to store thread-sensitive data. - - parameter userInfoKey: the key for custom data. Make sure this is a static pointer that will never be changed. - - returns: A custom data identified by `userInfoKey` */ - public subscript(userInfoKey key: UnsafeRawPointer) -> Any? { - - get { - - self.userInfoLock.lock() - defer { - - self.userInfoLock.unlock() - } - return self.userInfo[key] - } - set { - - self.userInfoLock.lock() - defer { - - self.userInfoLock.unlock() - } - self.userInfo[key] = newValue - } - } - - /** - Allow external libraries to store custom data in the transaction. App code should rarely have a need for this. - ``` - enum Static { - static var myDataKey: Void? - } - CoreStore.defaultStack[userInfoKey: &Static.myDataKey, lazyInit: { MyObject() }] = myObject - ``` - - Important: Do not use this method to store thread-sensitive data. - - parameter userInfoKey: the key for custom data. Make sure this is a static pointer that will never be changed. - - parameter lazyInit: a closure to use to lazily-initialize the data - - returns: A custom data identified by `userInfoKey` - */ - public subscript(userInfoKey key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any { - - self.userInfoLock.lock() - defer { - - self.userInfoLock.unlock() - } - if let value = self.userInfo[key] { - - return value - } - let value = closure() - self.userInfo[key] = value - return value - } + private let userInfo = UserInfo() // MARK: Internal @@ -542,10 +491,4 @@ public /*abstract*/ class BaseDataTransaction { return self.bypassesQueueing || self.transactionQueue.cs_isCurrentExecutionContext() } - - - // MARK: Private - - private var userInfo: [UnsafeRawPointer: Any] = [:] - private let userInfoLock = NSRecursiveLock() } From 02a660e4a623acb51aed8d93bf99518f4d0a56d7 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Thu, 20 Apr 2017 20:26:11 +0900 Subject: [PATCH 08/14] WIP: Migrations --- CoreStore.xcodeproj/project.pbxproj | 78 ++++++++++++------- ...g.swift => CoreStoreObject+Querying.swift} | 39 +++++++++- ...reStore+CustomDebugStringConvertible.swift | 8 +- .../MigrationMappingProvider.swift | 36 +++++++++ Sources/Setup/DataStack.swift | 4 +- .../Dynamic Models/CoreStoreObject.swift | 29 ++++++- .../Dynamic Schema/DynamicSchema.swift | 12 +++ ...swift => LegacyXcodeDataModelSchema.swift} | 6 +- ...Model.swift => XcodeDataModelSchema.swift} | 6 +- .../Setup/Dynamic Models/DynamicObject.swift | 40 +++------- .../Setup/Dynamic Models/SchemaHistory.swift | 24 +++++- 11 files changed, 205 insertions(+), 77 deletions(-) rename Sources/Fetching and Querying/{Attribute+Querying.swift => CoreStoreObject+Querying.swift} (74%) create mode 100644 Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift rename Sources/Setup/Dynamic Models/Dynamic Schema/{LegacyXcodeDataModel.swift => LegacyXcodeDataModelSchema.swift} (92%) rename Sources/Setup/Dynamic Models/Dynamic Schema/{XcodeDataModel.swift => XcodeDataModelSchema.swift} (95%) diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 8be28c6..a1a3ed0 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -214,14 +214,14 @@ B52F743E1E9B8724005F3DAC /* DynamicSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */; }; B52F743F1E9B8724005F3DAC /* DynamicSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */; }; B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */; }; - B52F74411E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */; }; - B52F74421E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */; }; - B52F74431E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */; }; - B52F74441E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */; }; - B52F74451E9B8724005F3DAC /* XcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */; }; - B52F74461E9B8724005F3DAC /* XcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */; }; - B52F74471E9B8724005F3DAC /* XcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */; }; - B52F74481E9B8724005F3DAC /* XcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */; }; + B52F74411E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */; }; + B52F74421E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */; }; + B52F74431E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */; }; + B52F74441E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */; }; + B52F74451E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */; }; + B52F74461E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */; }; + B52F74471E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */; }; + B52F74481E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */; }; B52F744A1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */; }; B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */; }; B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */; }; @@ -468,10 +468,10 @@ B5D339E81E9493A500C880DE /* Entity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339E61E9493A500C880DE /* Entity.swift */; }; B5D339E91E9493A500C880DE /* Entity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339E61E9493A500C880DE /* Entity.swift */; }; B5D339EA1E9493A500C880DE /* Entity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339E61E9493A500C880DE /* Entity.swift */; }; - B5D339EC1E9495E500C880DE /* Attribute+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */; }; - B5D339ED1E9495E500C880DE /* Attribute+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */; }; - B5D339EE1E9495E500C880DE /* Attribute+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */; }; - B5D339EF1E9495E500C880DE /* Attribute+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */; }; + B5D339EC1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */; }; + B5D339ED1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */; }; + B5D339EE1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */; }; + B5D339EF1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */; }; B5D339F11E94AF5800C880DE /* CoreStoreStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339F01E94AF5800C880DE /* CoreStoreStrings.swift */; }; B5D339F21E94AF5800C880DE /* CoreStoreStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339F01E94AF5800C880DE /* CoreStoreStrings.swift */; }; B5D339F31E94AF5800C880DE /* CoreStoreStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339F01E94AF5800C880DE /* CoreStoreStrings.swift */; }; @@ -531,6 +531,10 @@ B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E222291CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift */; }; B5E2222D1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E222291CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift */; }; B5E2222E1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E222291CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift */; }; + B5E41EBB1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; + B5E41EBC1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; + B5E41EBD1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; + B5E41EBE1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */; }; B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834BA1B7691F3001D3D50 /* Functions.swift */; }; B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; }; @@ -702,8 +706,8 @@ B52DD17D1BE1F8CC00949AFE /* CoreStoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreStoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaHistory.swift; sourceTree = ""; }; B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicSchema.swift; sourceTree = ""; }; - B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyXcodeDataModel.swift; sourceTree = ""; }; - B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XcodeDataModel.swift; sourceTree = ""; }; + B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyXcodeDataModelSchema.swift; sourceTree = ""; }; + B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XcodeDataModelSchema.swift; sourceTree = ""; }; B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreSchema.swift; sourceTree = ""; }; 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 = ""; }; @@ -774,7 +778,7 @@ B5D339DC1E9489C700C880DE /* DynamicObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicObject.swift; sourceTree = ""; }; B5D339E11E948C3600C880DE /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; B5D339E61E9493A500C880DE /* Entity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Entity.swift; sourceTree = ""; }; - B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Attribute+Querying.swift"; sourceTree = ""; }; + B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+Querying.swift"; sourceTree = ""; }; B5D339F01E94AF5800C880DE /* CoreStoreStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreStrings.swift; sourceTree = ""; }; B5D33A001E96012400C880DE /* Relationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Relationship.swift; sourceTree = ""; }; B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; @@ -796,6 +800,7 @@ B5E1B5A71CAA49E2007FD580 /* CSDataStack+Migrating.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSDataStack+Migrating.swift"; sourceTree = ""; }; B5E222221CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSynchronousDataTransaction.swift; sourceTree = ""; }; B5E222291CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSUnsafeDataTransaction.swift; sourceTree = ""; }; + B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationMappingProvider.swift; sourceTree = ""; }; B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseDataTransaction+Importing.swift"; sourceTree = ""; }; B5E834BA1B7691F3001D3D50 /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; B5E84ED81AFF82360064E85B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; @@ -1052,8 +1057,8 @@ children = ( B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */, B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */, - B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */, - B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */, + B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */, + B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */, ); path = "Dynamic Schema"; sourceTree = ""; @@ -1130,6 +1135,7 @@ B56007151B4018AB00A9A8F9 /* MigrationChain.swift */, B5A261201B64BFDB006EB6D3 /* MigrationType.swift */, B56965231B356B820075EE4A /* MigrationResult.swift */, + B5E41EB91EA8C3A1006240F0 /* Migration Mapping Providers */, ); path = Migrating; sourceTree = ""; @@ -1220,6 +1226,14 @@ name = "Fetching and Querying"; sourceTree = ""; }; + B5E41EB91EA8C3A1006240F0 /* Migration Mapping Providers */ = { + isa = PBXGroup; + children = ( + B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */, + ); + path = "Migration Mapping Providers"; + sourceTree = ""; + }; B5E834B61B7630BD001D3D50 /* Importing */ = { isa = PBXGroup; children = ( @@ -1272,7 +1286,7 @@ B5E84EFD1AFF847B0064E85B /* Fetching and Querying */ = { isa = PBXGroup; children = ( - B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */, + B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */, B5E84EFE1AFF847B0064E85B /* BaseDataTransaction+Querying.swift */, B5E84F061AFF847B0064E85B /* DataStack+Querying.swift */, B5E84F071AFF847B0064E85B /* CoreStore+Querying.swift */, @@ -1696,7 +1710,7 @@ B5E1B5981CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, B5519A5F1CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52FD3AA1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, - B52F74411E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, + B52F74411E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */, B51FE5AB1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, B5A9921F1EA898710091A2E3 /* UserInfo.swift in Sources */, B54A6A551BA15F2A007870FD /* FetchedResultsControllerDelegate.swift in Sources */, @@ -1750,7 +1764,7 @@ B5ECDC111CA816E500C7F112 /* CSTweak.swift in Sources */, B5E84F411AFF8CCD0064E85B /* ClauseTypes.swift in Sources */, B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */, - B52F74451E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, + B52F74451E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, B5FAD6AC1B51285300714891 /* MigrationManager.swift in Sources */, B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */, B5FEC18E1C9166E200532541 /* NSPersistentStore+Setup.swift in Sources */, @@ -1769,7 +1783,7 @@ B52F742F1E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B5E84EF51AFF846E0064E85B /* BaseDataTransaction.swift in Sources */, B5E84EFB1AFF846E0064E85B /* SaveResult.swift in Sources */, - B5D339EC1E9495E500C880DE /* Attribute+Querying.swift in Sources */, + B5D339EC1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B5E84F0F1AFF847B0064E85B /* From.swift in Sources */, B5FAD6A91B50A4B400714891 /* Progress+Convenience.swift in Sources */, B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */, @@ -1801,6 +1815,7 @@ B5A991EC1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */, B5FE4DA71C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, B52F743D1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, + B5E41EBB1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */, B5ECDBEC1CA6BF2000C7F112 /* CSFrom.swift in Sources */, B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */, B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */, @@ -1869,7 +1884,7 @@ B5E1B59A1CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, B5519A601CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52FD3AB1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, - B52F74421E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, + B52F74421E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */, B51FE5AD1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */, B5FE4DAD1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, @@ -1923,7 +1938,7 @@ B5ECDC131CA816E500C7F112 /* CSTweak.swift in Sources */, 82BA18C91C4BBD5900A0916E /* MigrationType.swift in Sources */, 82BA18D01C4BBD7100A0916E /* MigrationManager.swift in Sources */, - B52F74461E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, + B52F74461E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, 82BA18C61C4BBD5900A0916E /* DataStack+Migration.swift in Sources */, B59851491C90289D00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B5E1B5A41CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, @@ -1941,7 +1956,7 @@ B52F74301E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, 82BA18C31C4BBD5300A0916E /* ObjectObserver.swift in Sources */, 82BA18BF1C4BBD5300A0916E /* SectionBy.swift in Sources */, - B5D339ED1E9495E500C880DE /* Attribute+Querying.swift in Sources */, + B5D339ED1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, 82BA18AC1C4BBD3100A0916E /* SynchronousDataTransaction.swift in Sources */, 82BA18C71C4BBD5900A0916E /* CoreStore+Migration.swift in Sources */, B5E222251CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, @@ -1974,6 +1989,7 @@ B5A991ED1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */, B5ECDBEE1CA6BF2000C7F112 /* CSFrom.swift in Sources */, B52F743E1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, + B5E41EBC1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */, 82BA18D61C4BBD7100A0916E /* NSManagedObjectContext+Transaction.swift in Sources */, 82BA18B91C4BBD4A00A0916E /* From.swift in Sources */, B53FBA061CAB300C00F0D40A /* CSMigrationType.swift in Sources */, @@ -2042,7 +2058,7 @@ B5ECDC211CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */, B52DD1C21BE1F94600949AFE /* MigrationManager.swift in Sources */, B52FD3AD1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, - B52F74441E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, + B52F74441E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */, B5ECDC2D1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */, B5D7A5BA1CA3BF8F005C752B /* CSInto.swift in Sources */, @@ -2096,7 +2112,7 @@ B5ECDC091CA8138100C7F112 /* CSOrderBy.swift in Sources */, B52DD1A51BE1F92F00949AFE /* ImportableUniqueObject.swift in Sources */, B5E222271CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, - B52F74481E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, + B52F74481E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, B5519A621CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52DD19C1BE1F92C00949AFE /* Into.swift in Sources */, B5FE4DA51C8481E100FA6A91 /* StorageInterface.swift in Sources */, @@ -2114,7 +2130,7 @@ B52F74321E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B52DD1A31BE1F92C00949AFE /* SaveResult.swift in Sources */, B5220E211D130816009BC71E /* CSObjectObserver.swift in Sources */, - B5D339EF1E9495E500C880DE /* Attribute+Querying.swift in Sources */, + B5D339EF1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B52DD19F1BE1F92C00949AFE /* SynchronousDataTransaction.swift in Sources */, B52DD1CB1BE1F94600949AFE /* WeakObject.swift in Sources */, B52DD1C11BE1F94600949AFE /* Functions.swift in Sources */, @@ -2147,6 +2163,7 @@ B5A991EF1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */, B5220E201D130813009BC71E /* CSObjectMonitor.swift in Sources */, B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */, + B5E41EBE1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */, B5220E171D1306DF009BC71E /* UnsafeDataTransaction+Observing.swift in Sources */, B53FBA081CAB300C00F0D40A /* CSMigrationType.swift in Sources */, B52DD1B91BE1F94000949AFE /* CoreStore+Migration.swift in Sources */, @@ -2215,7 +2232,7 @@ B5519A611CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B5FE4DAE1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, B52FD3AC1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, - B52F74431E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, + B52F74431E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */, B51FE5AE1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */, B563218C1BD65216006C9394 /* DataStack+Transaction.swift in Sources */, @@ -2269,7 +2286,7 @@ B56321931BD65216006C9394 /* DataStack+Querying.swift in Sources */, B56321A71BD65216006C9394 /* MigrationResult.swift in Sources */, B598514A1C90289E00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, - B52F74471E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, + B52F74471E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, B5FEC1901C9166E700532541 /* NSPersistentStore+Setup.swift in Sources */, B56321A11BD65216006C9394 /* ListMonitor.swift in Sources */, B5E1B5A51CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, @@ -2287,7 +2304,7 @@ B52F74311E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B563218F1BD65216006C9394 /* ImportableObject.swift in Sources */, B56321991BD65216006C9394 /* OrderBy.swift in Sources */, - B5D339EE1E9495E500C880DE /* Attribute+Querying.swift in Sources */, + B5D339EE1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B56321A51BD65216006C9394 /* MigrationChain.swift in Sources */, B563218E1BD65216006C9394 /* SaveResult.swift in Sources */, B5E222261CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, @@ -2320,6 +2337,7 @@ B5A991EE1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */, B5ECDBEF1CA6BF2000C7F112 /* CSFrom.swift in Sources */, B52F743F1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, + B5E41EBD1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */, B56321B41BD6521C006C9394 /* NSManagedObjectContext+Transaction.swift in Sources */, B56321861BD65216006C9394 /* CoreStoreLogger.swift in Sources */, B53FBA071CAB300C00F0D40A /* CSMigrationType.swift in Sources */, diff --git a/Sources/Fetching and Querying/Attribute+Querying.swift b/Sources/Fetching and Querying/CoreStoreObject+Querying.swift similarity index 74% rename from Sources/Fetching and Querying/Attribute+Querying.swift rename to Sources/Fetching and Querying/CoreStoreObject+Querying.swift index ad9f4cc..f2419bf 100644 --- a/Sources/Fetching and Querying/Attribute+Querying.swift +++ b/Sources/Fetching and Querying/CoreStoreObject+Querying.swift @@ -1,5 +1,5 @@ // -// Attribute+Querying.swift +// CoreStoreObject+Querying.swift // CoreStore // // Copyright © 2017 John Rommel Estropia @@ -27,30 +27,65 @@ import CoreData import Foundation +// MARK: - DynamicObject + +public extension DynamicObject where Self: CoreStoreObject { + + @inline(__always) + public static func keyPath(_ attribute: (Self) -> ValueContainer.Required) -> String { + + return attribute(self.meta).keyPath + } + + @inline(__always) + public static func keyPath(_ attribute: (Self) -> ValueContainer.Optional) -> String { + + return attribute(self.meta).keyPath + } + + @inline(__always) + public static func `where`(_ condition: (Self) -> Where) -> Where { + + return condition(self.meta) + } +} + + // MARK: - ValueContainer.Required public extension ValueContainer.Required { + @inline(__always) public static func == (_ attribute: ValueContainer.Required, _ value: V) -> Where { return Where(attribute.keyPath, isEqualTo: value) } + + @inline(__always) public static func < (_ attribute: ValueContainer.Required, _ value: V) -> Where { return Where("%K < %@", attribute.keyPath, value) } + + @inline(__always) public static func > (_ attribute: ValueContainer.Required, _ value: V) -> Where { return Where("%K > %@", attribute.keyPath, value) } + + @inline(__always) public static func <= (_ attribute: ValueContainer.Required, _ value: V) -> Where { return Where("%K <= %@", attribute.keyPath, value) } + + @inline(__always) public static func >= (_ attribute: ValueContainer.Required, _ value: V) -> Where { return Where("%K >= %@", attribute.keyPath, value) } + + @inline(__always) public static func != (_ attribute: ValueContainer.Required, _ value: V) -> Where { return !Where(attribute.keyPath, isEqualTo: value) @@ -62,11 +97,13 @@ public extension ValueContainer.Required { public extension ValueContainer.Optional { + @inline(__always) public static func == (_ attribute: ValueContainer.Optional, _ value: V?) -> Where { return Where(attribute.keyPath, isEqualTo: value) } + @inline(__always) public static func != (_ attribute: ValueContainer.Optional, _ value: V?) -> Where { return !Where(attribute.keyPath, isEqualTo: value) diff --git a/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift b/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift index ecce091..0fbb2c5 100644 --- a/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift +++ b/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift @@ -400,9 +400,9 @@ extension LegacySQLiteStore: CustomDebugStringConvertible, CoreStoreDebugStringC } -// MARK: - LegacyXcodeDataModel +// MARK: - LegacyXcodeDataModelSchema -extension LegacyXcodeDataModel: CustomDebugStringConvertible, CoreStoreDebugStringConvertible { +extension LegacyXcodeDataModelSchema: CustomDebugStringConvertible, CoreStoreDebugStringConvertible { // MARK: CustomDebugStringConvertible @@ -1045,9 +1045,9 @@ extension VersionLock: CustomStringConvertible, CustomDebugStringConvertible, Co } -// MARK: - XcodeDataModel +// MARK: - XcodeDataModelSchema -extension XcodeDataModel: CustomDebugStringConvertible, CoreStoreDebugStringConvertible { +extension XcodeDataModelSchema: CustomDebugStringConvertible, CoreStoreDebugStringConvertible { // MARK: CustomDebugStringConvertible diff --git a/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift b/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift new file mode 100644 index 0000000..61fcdda --- /dev/null +++ b/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift @@ -0,0 +1,36 @@ +// +// MigrationMappingProvider.swift +// CoreStore +// +// Copyright © 2017 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import CoreData +import Foundation + + +// MARK: - MigrationMappingProvider + +public protocol MigrationMappingProvider { + + associatedtype SourceType: DynamicObject + associatedtype DestinationType: DynamicObject +} diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index f07c32b..715caf4 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -611,14 +611,14 @@ public final class DataStack: Equatable { - parameter model: the `NSManagedObjectModel` for the stack - parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack. */ - @available(*, deprecated: 3.1, message: "Use the new DataStack.init(schemaHistory:) initializer passing a LegacyXcodeDataModel instance as argument") + @available(*, deprecated: 3.1, message: "Use the new DataStack.init(schemaHistory:) initializer passing a LegacyXcodeDataModelSchema instance as argument") public convenience init(model: NSManagedObjectModel, migrationChain: MigrationChain = nil) { let modelVersion = migrationChain.leafVersions.first! self.init( schemaHistory: SchemaHistory( allSchema: [ - LegacyXcodeDataModel( + LegacyXcodeDataModelSchema( modelName: modelVersion, model: model ) diff --git a/Sources/Setup/Dynamic Models/CoreStoreObject.swift b/Sources/Setup/Dynamic Models/CoreStoreObject.swift index 6a2a74d..d640b3a 100644 --- a/Sources/Setup/Dynamic Models/CoreStoreObject.swift +++ b/Sources/Setup/Dynamic Models/CoreStoreObject.swift @@ -29,8 +29,31 @@ import Foundation // MARK: - CoreStoreObject -open class CoreStoreObject: DynamicObject, Hashable { +/** + The `CoreStoreObject` is an abstract class for creating CoreStore-managed objects that are more type-safe and more convenient than `NSManagedObject` subclasses. The model entities for `CoreStoreObject` subclasses are inferred from the subclasses' Swift declaration themselves; no .xcdatamodeld files needed. To declare persisted attributes and relationships for the `CoreStoreObject` subclass, declare properties of type `Value.Required`, `Value.Optional` for values, or `Relationship.ToOne`, `Relationship.ToManyOrdered`, `Relationship.ToManyUnordered` for relationships. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species") + let nickname = Value.Optional("nickname") + let master = Relationship.ToOne("master") + } + + class Person: CoreStoreObject { + let name = Value.Required("name") + let pet = Relationship.ToOne("pet", inverse: { $0.master }) + } + ``` + `CoreStoreObject` entities for a model version should be added to `CoreStoreSchema` instance. + - SeeAlso: CoreStoreSchema + - SeeAlso: CoreStoreObject.Value + - SeeAlso: CoreStoreObject.Relationship + */ +open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable { + /** + Do not call this directly. This is exposed as public only as a required initializer. + - Important: subclasses that need a custom initializer should override both `init(_:)` and `init(asMeta:)`, and to call their corresponding super implementations. + */ public required init(_ object: NSManagedObject) { self.isMeta = false @@ -38,6 +61,10 @@ open class CoreStoreObject: DynamicObject, Hashable { self.initializeAttributes(Mirror(reflecting: self), { [unowned object] in object }) } + /** + Do not call this directly. This is exposed as public only as a required initializer. + - Important: subclasses that need a custom initializer should override both `init(_:)` and `init(asMeta:)`, and to call their corresponding super implementations. + */ public required init(asMeta: Void) { self.isMeta = true diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift index f18123a..09a7bf5 100644 --- a/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift @@ -29,9 +29,21 @@ import Foundation // MARK: - DynamicSchema +/** + `DynamicSchema` are types that provide `NSManagedObjectModel` instances for a particular model version. CoreStore currently supports concrete types: + - `XcodeDataModelSchema`: describes models loaded from a .xcdatamodeld file. + - `LegacyXcodeDataModelSchema`: describes models loaded directly from an existing `NSManagedObjectModel`. It is not advisable to continue using this model as its metadata are not available to CoreStore. + - `CoreStoreSchema`: describes models written in `CoreStoreObject` Swift class declarations. + */ public protocol DynamicSchema { + /** + The version string for this model schema. + */ var modelVersion: ModelVersion { get } + /** + Do not call this directly. The `NSManagedObjectModel` for this schema may be created lazily and using this method directly may affect the integrity of the model. + */ func rawModel() -> NSManagedObjectModel } diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModel.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModelSchema.swift similarity index 92% rename from Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModel.swift rename to Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModelSchema.swift index 741d5f8..d9db734 100644 --- a/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModel.swift +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModelSchema.swift @@ -1,5 +1,5 @@ // -// LegacyXcodeDataModel.swift +// LegacyXcodeDataModelSchema.swift // CoreStore // // Copyright © 2017 John Rommel Estropia @@ -27,9 +27,9 @@ import CoreData import Foundation -// MARK: - LegacyXcodeDataModel +// MARK: - LegacyXcodeDataModelSchema -public final class LegacyXcodeDataModel: DynamicSchema { +public final class LegacyXcodeDataModelSchema: DynamicSchema { public required init(modelName: ModelVersion, model: NSManagedObjectModel) { diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModel.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModelSchema.swift similarity index 95% rename from Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModel.swift rename to Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModelSchema.swift index 94f1cc2..8e25ebc 100644 --- a/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModel.swift +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModelSchema.swift @@ -1,5 +1,5 @@ // -// XcodeDataModel.swift +// XcodeDataModelSchema.swift // CoreStore // // Copyright © 2017 John Rommel Estropia @@ -27,9 +27,9 @@ import CoreData import Foundation -// MARK: - XcodeDataModel +// MARK: - XcodeDataModelSchema -public final class XcodeDataModel: DynamicSchema { +public final class XcodeDataModelSchema: DynamicSchema { public required init(modelVersion: ModelVersion, modelVersionFileURL: URL) { diff --git a/Sources/Setup/Dynamic Models/DynamicObject.swift b/Sources/Setup/Dynamic Models/DynamicObject.swift index 2a09c09..deb7841 100644 --- a/Sources/Setup/Dynamic Models/DynamicObject.swift +++ b/Sources/Setup/Dynamic Models/DynamicObject.swift @@ -37,35 +37,6 @@ public protocol DynamicObject: class { func cs_toRaw() -> NSManagedObject } -public extension DynamicObject where Self: CoreStoreObject { - - @inline(__always) - public static func keyPath(_ attribute: (Self) -> ValueContainer.Required) -> String { - - return attribute(self.meta).keyPath - } - - @inline(__always) - public static func keyPath(_ attribute: (Self) -> ValueContainer.Optional) -> String { - - return attribute(self.meta).keyPath - } - - @inline(__always) - public static func `where`(_ condition: (Self) -> Where) -> Where { - - return condition(self.meta) - } - - - // MARK: Internal - - internal static var meta: Self { - - return self.init(asMeta: ()) - } -} - // MARK: - NSManagedObject @@ -127,3 +98,14 @@ extension CoreStoreObject { return self.rawObject! } } + + +// MARK: - Internal + +internal extension DynamicObject where Self: CoreStoreObject { + + internal static var meta: Self { + + return self.init(asMeta: ()) + } +} diff --git a/Sources/Setup/Dynamic Models/SchemaHistory.swift b/Sources/Setup/Dynamic Models/SchemaHistory.swift index c6dc33f..2921a5f 100644 --- a/Sources/Setup/Dynamic Models/SchemaHistory.swift +++ b/Sources/Setup/Dynamic Models/SchemaHistory.swift @@ -29,14 +29,30 @@ import Foundation // MARK: - SchemaHistory +/** + The `SchemaHistory` encapsulates all model versions that is relevant to the data model, including past versions. + - SeeAlso: SchemaHistory.currentModelVersion + - SeeAlso: SchemaHistory.migrationChain + */ public final class SchemaHistory: ExpressibleByArrayLiteral { - - // MARK: - - + /** + The version string for the current model version. The `DataStack` will try to migrate all `StorageInterface`s added to itself to this version, following the version steps provided by the `migrationChain`. + */ public let currentModelVersion: ModelVersion + + /** + The version string for the current model version. The `DataStack` will try to migrate all `StorageInterface`s added to itself to this version, following the version steps provided by the `migrationChain`. + */ public let migrationChain: MigrationChain + /** + Initializes a `SchemaHistory` with all models declared in the specified (.xcdatamodeld) model file. + - Important: Use this initializer only if all model versions are either `XcodeDataModelSchema`s or `LegacyXcodeDataModelSchema`s. Do not use this initializer if even one of the model versions is a `CoreStoreSchema`; use the `SchemaHistory.init(allSchema:migrationChain:exactCurrentModelVersion:)` initializer instead. + - parameter modelName: the name of the (.xcdatamodeld) model file. If not specified, the application name (CFBundleName) will be used if it exists, or "CoreData" if it the bundle name was not set. + - parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used. + - parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack. + */ public convenience init(modelName: XcodeDataModelFileName, bundle: Bundle = Bundle.main, migrationChain: MigrationChain = nil) { guard let modelFilePath = bundle.path(forResource: modelName, ofType: "momd") else { @@ -92,7 +108,7 @@ public final class SchemaHistory: ExpressibleByArrayLiteral { for modelVersion in modelVersions { let fileURL = modelFileURL.appendingPathComponent("\(modelVersion).mom", isDirectory: false) - allSchema.append(XcodeDataModel(modelVersion: modelVersion, modelVersionFileURL: fileURL)) + allSchema.append(XcodeDataModelSchema(modelVersion: modelVersion, modelVersionFileURL: fileURL)) } self.init( allSchema: allSchema, From fe70b7a27d6515a5fcddced32fedb94573b0a87c Mon Sep 17 00:00:00 2001 From: John Estropia Date: Fri, 21 Apr 2017 14:54:57 +0900 Subject: [PATCH 09/14] added tool to convert existing NSManagedObjectModels to the new CoreStoreSchema --- CoreStore.xcodeproj/project.pbxproj | 10 + CoreStoreDemo/CoreStoreDemo/AppDelegate.swift | 42 +++ .../Palette.swift | 2 +- .../DynamicSchema+Convenience.swift | 249 ++++++++++++++++++ .../NSManagedObject+Convenience.swift | 33 +-- Sources/CoreStoreStrings.swift | 2 +- .../Importing/ImportableUniqueObject.swift | 2 +- .../NSEntityDescription+DynamicModel.swift | 16 +- .../Internal/NSManagedObject+Logging.swift | 19 ++ .../MigrationMappingProvider.swift | 76 +++++- .../NSManagedObject+ObjectiveC.swift | 4 +- Sources/Setup/DataStack.swift | 8 + .../Dynamic Schema/CoreStoreSchema.swift | 27 +- Sources/Setup/Dynamic Models/Entity.swift | 17 +- .../Setup/Dynamic Models/Relationship.swift | 64 +++-- Sources/Setup/Dynamic Models/Value.swift | 12 +- 16 files changed, 517 insertions(+), 66 deletions(-) create mode 100644 Sources/Convenience/DynamicSchema+Convenience.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index a1a3ed0..4c2be98 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -535,6 +535,10 @@ B5E41EBC1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; B5E41EBD1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; B5E41EBE1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; + B5E41EC01EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; + B5E41EC11EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; + B5E41EC21EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; + B5E41EC31EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */; }; B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834BA1B7691F3001D3D50 /* Functions.swift */; }; B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; }; @@ -801,6 +805,7 @@ B5E222221CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSynchronousDataTransaction.swift; sourceTree = ""; }; B5E222291CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSUnsafeDataTransaction.swift; sourceTree = ""; }; B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationMappingProvider.swift; sourceTree = ""; }; + B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DynamicSchema+Convenience.swift"; sourceTree = ""; }; B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseDataTransaction+Importing.swift"; sourceTree = ""; }; B5E834BA1B7691F3001D3D50 /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; B5E84ED81AFF82360064E85B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; @@ -1343,6 +1348,7 @@ B5FAD6A81B50A4B300714891 /* Progress+Convenience.swift */, B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */, B5A9921E1EA898710091A2E3 /* UserInfo.swift */, + B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */, ); path = Convenience; sourceTree = ""; @@ -1759,6 +1765,7 @@ B56965241B356B820075EE4A /* MigrationResult.swift in Sources */, B5FE4DAC1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, B501FDE71CA8D20500BE22EF /* CSListObserver.swift in Sources */, + B5E41EC01EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */, B501FDE21CA8D1F500BE22EF /* CSListMonitor.swift in Sources */, 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */, B5ECDC111CA816E500C7F112 /* CSTweak.swift in Sources */, @@ -1933,6 +1940,7 @@ 82BA18D41C4BBD7100A0916E /* NSManagedObjectContext+Querying.swift in Sources */, 82BA18D51C4BBD7100A0916E /* NSManagedObjectContext+Setup.swift in Sources */, B501FDE91CA8D20500BE22EF /* CSListObserver.swift in Sources */, + B5E41EC11EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */, B501FDE41CA8D1F500BE22EF /* CSListMonitor.swift in Sources */, B5FE4DA31C8481E100FA6A91 /* StorageInterface.swift in Sources */, B5ECDC131CA816E500C7F112 /* CSTweak.swift in Sources */, @@ -2107,6 +2115,7 @@ B52DD19D1BE1F92C00949AFE /* BaseDataTransaction.swift in Sources */, B5220E131D1305ED009BC71E /* SectionBy.swift in Sources */, B559CD4D1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, + B5E41EC31EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */, B5ECDBE91CA6BEA300C7F112 /* CSClauseTypes.swift in Sources */, B52DD1B81BE1F94000949AFE /* DataStack+Migration.swift in Sources */, B5ECDC091CA8138100C7F112 /* CSOrderBy.swift in Sources */, @@ -2281,6 +2290,7 @@ B56321B31BD6521C006C9394 /* NSManagedObjectContext+Setup.swift in Sources */, B501FDEA1CA8D20500BE22EF /* CSListObserver.swift in Sources */, B501FDE51CA8D1F500BE22EF /* CSListMonitor.swift in Sources */, + B5E41EC21EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */, B5ECDC141CA816E500C7F112 /* CSTweak.swift in Sources */, B56321AE1BD6521C006C9394 /* NotificationObserver.swift in Sources */, B56321931BD65216006C9394 /* DataStack+Querying.swift in Sources */, diff --git a/CoreStoreDemo/CoreStoreDemo/AppDelegate.swift b/CoreStoreDemo/CoreStoreDemo/AppDelegate.swift index 2e0af90..59d7571 100644 --- a/CoreStoreDemo/CoreStoreDemo/AppDelegate.swift +++ b/CoreStoreDemo/CoreStoreDemo/AppDelegate.swift @@ -8,6 +8,7 @@ import UIKit +import CoreStore // MARK: - AppDelegate @@ -22,6 +23,47 @@ class AppDelegate: UIResponder, UIApplicationDelegate { application.statusBarStyle = .lightContent + /// Generated by CoreStore on 4/21/17, 2:41 PM + class Place: CoreStoreObject { + + let latitude = Value.Optional("latitude", default: 0.0) + let title = Value.Optional("title") + let longitude = Value.Optional("longitude", default: 0.0) + let subtitle = Value.Optional("subtitle") + } + class Palette: CoreStoreObject { + + let saturation = Value.Optional("saturation", default: 0.0) + let hue = Value.Optional("hue", default: 0) + let brightness = Value.Optional("brightness", default: 0.0) + let colorName = Value.Optional("colorName", isTransient: true) + } + class TimeZone: CoreStoreObject { + + let secondsFromGMT = Value.Optional("secondsFromGMT", default: 0) + let name = Value.Optional("name") + let daylightSavingTimeOffset = Value.Optional("daylightSavingTimeOffset", default: 0.0) + let abbreviation = Value.Optional("abbreviation") + let hasDaylightSavingTime = Value.Optional("hasDaylightSavingTime") + } + + + + let schema = CoreStoreSchema( + modelVersion: "CoreStoreDemo", + entities: [ + Entity("Place"), + Entity("Palette"), + Entity("TimeZone"), + ], + versionLock: [ + "Place": [0x25cb5bd001887b92, 0xfe86dd433a5e0430, 0xcca50ac3f3659b68, 0xfe4e494ff66439b0], + "Palette": [0xa306515d026d3c43, 0x1b299716733e56f6, 0x53bff8954221a1b6, 0xa74d6b1e613923ab], + "TimeZone": [0x92e08db969e46163, 0xae9cf1ab738868c5, 0xb6a269249771a562, 0x58a357eab4c99ed5] + ] + ) + + print(schema.printCoreStoreSchema()) return true } } diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift index 74e4888..fffc643 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift @@ -49,7 +49,7 @@ class Palette: NSManagedObject { } set { - self.setValue(newValue, forKvcKey: #keyPath(Palette.colorName)) + self.setValue(newValue.cs_toImportableNativeType(), forKvcKey: #keyPath(Palette.colorName)) } } diff --git a/Sources/Convenience/DynamicSchema+Convenience.swift b/Sources/Convenience/DynamicSchema+Convenience.swift new file mode 100644 index 0000000..610e02c --- /dev/null +++ b/Sources/Convenience/DynamicSchema+Convenience.swift @@ -0,0 +1,249 @@ +// +// DynamicSchema+Convenience.swift +// CoreStore +// +// Copyright © 2017 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import CoreData +import Foundation + + +// MARK: - DynamicSchema + +public extension DynamicSchema { + + public func printCoreStoreSchema() -> String { + + let model = self.rawModel() + let entitiesByName = model.entitiesByName + + var output = "/// Generated by CoreStore on \(DateFormatter.localizedString(from: Date(), dateStyle: .short, timeStyle: .short))\n" + var addedInverse: Set = [] + for (entityName, entity) in entitiesByName { + + let superName: String + if let superEntity = entity.superentity { + + superName = superEntity.name! + } + else { + + superName = String(describing: CoreStoreObject.self) + } + output.append("class \(entityName): \(superName) {\n") + defer { + + output.append("}\n") + } + + let attributesByName = entity.attributesByName + if !attributesByName.isEmpty { + + output.append(" \n") + for (attributeName, attribute) in attributesByName { + + let containerType: String + if attribute.isOptional { + + containerType = "Value.Optional" + } + else { + + containerType = "Value.Required" + } + let valueType: Any.Type + var defaultString = "" + switch attribute.attributeType { + + case .integer16AttributeType: + valueType = Int16.self + if let defaultValue = (attribute.defaultValue as! Int16.ImportableNativeType?).flatMap(Int16.cs_fromImportableNativeType), + defaultValue != Int16.cs_emptyValue() { + + defaultString = ", default: \(defaultValue)" + } + case .integer32AttributeType: + valueType = Int32.self + if let defaultValue = (attribute.defaultValue as! Int32.ImportableNativeType?).flatMap(Int32.cs_fromImportableNativeType), + defaultValue != Int32.cs_emptyValue() { + + defaultString = ", default: \(defaultValue)" + } + case .integer64AttributeType: + valueType = Int64.self + if let defaultValue = (attribute.defaultValue as! Int64.ImportableNativeType?).flatMap(Int64.cs_fromImportableNativeType), + defaultValue != Int54.cs_emptyValue() { + + defaultString = ", default: \(defaultValue)" + } + case .decimalAttributeType: + valueType = NSDecimalNumber.self + if let defaultValue = (attribute.defaultValue as! NSDecimalNumber.ImportableNativeType?).flatMap(NSDecimalNumber.cs_fromImportableNativeType), + defaultValue != NSDecimalNumber.cs_emptyValue() { + + defaultString = ", default: NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")" + } + case .doubleAttributeType: + valueType = Double.self + if let defaultValue = (attribute.defaultValue as! Double.ImportableNativeType?).flatMap(Double.cs_fromImportableNativeType), + defaultValue != Double.cs_emptyValue() { + + defaultString = ", default: \(defaultValue)" + } + case .floatAttributeType: + valueType = Float.self + if let defaultValue = (attribute.defaultValue as! Float.ImportableNativeType?).flatMap(Float.cs_fromImportableNativeType), + defaultValue != Float.cs_emptyValue() { + + defaultString = ", default: \(defaultValue)" + } + case .stringAttributeType: + valueType = String.self + if let defaultValue = (attribute.defaultValue as! String.ImportableNativeType?).flatMap(String.cs_fromImportableNativeType), + defaultValue != String.cs_emptyValue() { + + // TODO: escape strings + defaultString = ", default: \"\(defaultValue)\"" + } + case .booleanAttributeType: + valueType = Bool.self + if let defaultValue = (attribute.defaultValue as! Bool.ImportableNativeType?).flatMap(Bool.cs_fromImportableNativeType), + defaultValue != Bool.cs_emptyValue() { + + defaultString = ", default: \(defaultValue ? "true" : "false")" + } + case .dateAttributeType: + valueType = Date.self + if let defaultValue = (attribute.defaultValue as! Date.ImportableNativeType?).flatMap(Date.cs_fromImportableNativeType), + defaultValue != Date.cs_emptyValue() { + + defaultString = ", default: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))" + } + case .binaryDataAttributeType: + valueType = Data.self + if let defaultValue = (attribute.defaultValue as! Data.ImportableNativeType?).flatMap(Data.cs_fromImportableNativeType), + defaultValue != Data.cs_emptyValue() { + + let count = defaultValue.count + let bytes = defaultValue.withUnsafeBytes { (pointer: UnsafePointer) in + + return (0 ..< (count / MemoryLayout.size)) + .map({ "\("0x\(String(pointer[$0], radix: 16, uppercase: false))")" }) + } + defaultString = ", default: Data(bytes: [\(bytes.joined(separator: ", "))])" + } + default: + fatalError("Unsupported attribute type: \(attribute.attributeType)") + } + let transientString = attribute.isTransient ? ", isTransient: true" : "" + output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(defaultString)\(transientString))\n") + } + } + + let relationshipsByName = entity.relationshipsByName + if !relationshipsByName.isEmpty { + + output.append(" \n") + for (relationshipName, relationship) in relationshipsByName { + + let containerType: String + var minCountString = "" + var maxCountString = "" + if relationship.isToMany { + + let minCount = relationship.minCount + let maxCount = relationship.maxCount + if relationship.isOrdered { + + containerType = "Relationship.ToManyOrdered" + } + else { + + containerType = "Relationship.ToManyUnordered" + } + if minCount > 0 { + + minCountString = ", minCount: \(minCount)" + } + if maxCount > 0 { + + maxCountString = ", maxCount: \(maxCount)" + } + } + else { + + containerType = "Relationship.ToOne" + } + var inverseString = "" + let relationshipQualifier = "\(entityName).\(relationshipName)" + if !addedInverse.contains(relationshipQualifier), + let inverseRelationship = relationship.inverseRelationship { + + inverseString = ", inverse: { $0.\(inverseRelationship.name) }" + addedInverse.insert("\(relationship.destinationEntity!.name!).\(inverseRelationship.name)") + } + var deleteRuleString = "" + if relationship.deleteRule != .nullifyDeleteRule { + + switch relationship.deleteRule { + + case .cascadeDeleteRule: + deleteRuleString = ", deleteRule: .cascade" + + case .denyDeleteRule: + deleteRuleString = ", deleteRule: .deny" + + case .nullifyDeleteRule: + deleteRuleString = ", deleteRule: .nullify" + + default: + fatalError("Unsupported delete rule \((relationship.deleteRule)) for relationship \"\(relationshipQualifier)\"") + } + } + output.append(" let \(relationshipName) = \(containerType)<\(relationship.destinationEntity!.name!)>(\"\(relationshipName)\"\(inverseString)\(deleteRuleString)\(minCountString)\(maxCountString))\n") + } + } + } + output.append("\n\n\n") + output.append("CoreStoreSchema(\n") + output.append(" modelVersion: \"\(self.modelVersion)\",\n") + output.append(" entities: [\n") + for (entityName, entity) in entitiesByName { + + var abstractString = "" + if entity.isAbstract { + + abstractString = ", isAbstract: true" + } + var versionHashModifierString = "" + if let versionHashModifier = entity.versionHashModifier { + + versionHashModifierString = ", versionHashModifier: \"\(versionHashModifier)\"" + } + output.append(" Entity<\(entityName)>(\"\(entityName)\"\(abstractString)\(versionHashModifierString)),\n") + } + output.append(" ],\n") + output.append(" versionLock: \(VersionLock(entityVersionHashesByName: model.entityVersionHashesByName).description.components(separatedBy: "\n").joined(separator: "\n "))\n") + output.append(")\n\n") + return output + } +} diff --git a/Sources/Convenience/NSManagedObject+Convenience.swift b/Sources/Convenience/NSManagedObject+Convenience.swift index fa60a9e..daf88b5 100644 --- a/Sources/Convenience/NSManagedObject+Convenience.swift +++ b/Sources/Convenience/NSManagedObject+Convenience.swift @@ -78,29 +78,29 @@ public extension NSManagedObject { } @nonobjc @inline(__always) - public func getValue(forKvcKey kvcKey: KeyPath) -> Any? { + public func getValue(forKvcKey kvcKey: KeyPath) -> CoreDataNativeType? { self.willAccessValue(forKey: kvcKey) defer { self.didAccessValue(forKey: kvcKey) } - return self.primitiveValue(forKey: kvcKey) + return self.primitiveValue(forKey: kvcKey) as! CoreDataNativeType? } @nonobjc @inline(__always) - public func getValue(forKvcKey kvcKey: KeyPath, didGetValue: (Any?) throws -> T) rethrows -> T { + public func getValue(forKvcKey kvcKey: KeyPath, didGetValue: (CoreDataNativeType?) throws -> T) rethrows -> T { self.willAccessValue(forKey: kvcKey) defer { self.didAccessValue(forKey: kvcKey) } - return try didGetValue(self.primitiveValue(forKey: kvcKey)) + return try didGetValue(self.primitiveValue(forKey: kvcKey) as! CoreDataNativeType?) } @nonobjc @inline(__always) - public func getValue(forKvcKey kvcKey: KeyPath, willGetValue: () throws -> Void, didGetValue: (Any?) throws -> T) rethrows -> T { + public func getValue(forKvcKey kvcKey: KeyPath, willGetValue: () throws -> Void, didGetValue: (CoreDataNativeType?) throws -> T) rethrows -> T { self.willAccessValue(forKey: kvcKey) defer { @@ -108,12 +108,11 @@ public extension NSManagedObject { self.didAccessValue(forKey: kvcKey) } try willGetValue() - return try didGetValue(self.primitiveValue(forKey: kvcKey)) + return try didGetValue(self.primitiveValue(forKey: kvcKey) as! CoreDataNativeType?) } @nonobjc @inline(__always) - @discardableResult - public func setValue(_ value: Any?, forKvcKey KVCKey: KeyPath) -> Any? { + public func setValue(_ value: CoreDataNativeType?, forKvcKey KVCKey: KeyPath) { self.willChangeValue(forKey: KVCKey) defer { @@ -121,12 +120,10 @@ public extension NSManagedObject { self.didChangeValue(forKey: KVCKey) } self.setPrimitiveValue(value, forKey: KVCKey) - return value } @nonobjc @inline(__always) - @discardableResult - public func setValue(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> Any?) rethrows -> T { + public func setValue(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> CoreDataNativeType?) rethrows { self.willChangeValue(forKey: KVCKey) defer { @@ -134,20 +131,6 @@ public extension NSManagedObject { self.didChangeValue(forKey: KVCKey) } self.setPrimitiveValue(try willSetValue(value), forKey: KVCKey) - return value - } - - @nonobjc @inline(__always) - @discardableResult - public func setValue(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> Any?, didSetValue: (T) -> T = { $0 }) rethrows -> T { - - self.willChangeValue(forKey: KVCKey) - defer { - - self.didChangeValue(forKey: KVCKey) - } - self.setPrimitiveValue(try willSetValue(value), forKey: KVCKey) - return didSetValue(value) } /** diff --git a/Sources/CoreStoreStrings.swift b/Sources/CoreStoreStrings.swift index 617ac54..5fc467b 100644 --- a/Sources/CoreStoreStrings.swift +++ b/Sources/CoreStoreStrings.swift @@ -45,7 +45,7 @@ public typealias ModelConfiguration = String? // MARK: - ModelVersion /** - An `String` that pertains to the name of a versioned *.xcdatamodeld file (without the file extension). + An `String` that pertains to the name of a versioned *.xcdatamodeld file (without the file extension). Model version strings don't necessarily have to be numeric or ordered in any way. The migration sequence will always be decided by (or the lack of) the `MigrationChain`. */ public typealias ModelVersion = String diff --git a/Sources/Importing/ImportableUniqueObject.swift b/Sources/Importing/ImportableUniqueObject.swift index 97f59fa..97e77d1 100644 --- a/Sources/Importing/ImportableUniqueObject.swift +++ b/Sources/Importing/ImportableUniqueObject.swift @@ -190,7 +190,7 @@ public extension ImportableUniqueObject where Self: DynamicObject { .setValue( newValue, forKvcKey: type(of: self).uniqueIDKeyPath, - willSetValue: { $0.cs_toImportableNativeType() } + willSetValue: { ($0.cs_toImportableNativeType() as! CoreDataNativeType) } ) } } diff --git a/Sources/Internal/NSEntityDescription+DynamicModel.swift b/Sources/Internal/NSEntityDescription+DynamicModel.swift index b5d91d8..bfce9af 100644 --- a/Sources/Internal/NSEntityDescription+DynamicModel.swift +++ b/Sources/Internal/NSEntityDescription+DynamicModel.swift @@ -38,23 +38,29 @@ internal extension NSEntityDescription { guard let userInfo = self.userInfo, let typeName = userInfo[UserInfoKey.CoreStoreManagedObjectTypeName] as! String?, - let entityName = userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] as! String? else { + let entityName = userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] as! String?, + let isAbstract = userInfo[UserInfoKey.CoreStoreManagedObjectIsAbstract] as! Bool? else { return nil } return CoreStoreSchema.AnyEntity( type: NSClassFromString(typeName) as! CoreStoreObject.Type, - entityName: entityName + entityName: entityName, + isAbstract: isAbstract, + versionHashModifier: userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] as! String? ) } set { if let newValue = newValue { - self.userInfo = [ + var userInfo: [AnyHashable : Any] = [ UserInfoKey.CoreStoreManagedObjectTypeName: NSStringFromClass(newValue.type), - UserInfoKey.CoreStoreManagedObjectEntityName: newValue.entityName + UserInfoKey.CoreStoreManagedObjectEntityName: newValue.entityName, + UserInfoKey.CoreStoreManagedObjectIsAbstract: newValue.isAbstract ] + userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] = newValue.versionHashModifier + self.userInfo = userInfo } else { @@ -72,5 +78,7 @@ internal extension NSEntityDescription { fileprivate static let CoreStoreManagedObjectTypeName = "CoreStoreManagedObjectTypeName" fileprivate static let CoreStoreManagedObjectEntityName = "CoreStoreManagedObjectEntityName" + fileprivate static let CoreStoreManagedObjectIsAbstract = "CoreStoreManagedObjectIsAbstract" + fileprivate static let CoreStoreManagedObjectVersionHashModifier = "CoreStoreManagedObjectVersionHashModifier" } } diff --git a/Sources/Internal/NSManagedObject+Logging.swift b/Sources/Internal/NSManagedObject+Logging.swift index 91dc4ba..2db850f 100644 --- a/Sources/Internal/NSManagedObject+Logging.swift +++ b/Sources/Internal/NSManagedObject+Logging.swift @@ -48,6 +48,25 @@ internal extension NSManagedObject { } return nil } + + @nonobjc + internal func isEditableInContext() -> Bool? { + + guard let context = self.managedObjectContext else { + + return nil + } + if context.isTransactionContext { + + return true + } + if context.isDataStackContext { + + return false + } + return nil + } + // TODO: test before release (rolled back) // @nonobjc // internal static func cs_swizzleMethodsForLogging() { diff --git a/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift b/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift index 61fcdda..52e1402 100644 --- a/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift +++ b/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift @@ -31,6 +31,78 @@ import Foundation public protocol MigrationMappingProvider { - associatedtype SourceType: DynamicObject - associatedtype DestinationType: DynamicObject + associatedtype SourceSchema: DynamicSchema + associatedtype DestinationSchema: DynamicSchema + + var sourceSchema: SourceSchema { get } + var destinationSchema: DestinationSchema { get } + + init(source: SourceSchema, destination: DestinationSchema) + +// func migrate( +// from oldObject: SourceType, +// to newObject: DestinationType, +// transaction: UnsafeDataTransaction +// ) +// +// func forEachPropertyMapping( +// from oldObject: SourceType, +// to newObject: DestinationType, +// removed: (_ keyPath: KeyPath) -> Void, +// added: (_ keyPath: KeyPath) -> Void, +// transformed: (_ keyPath: KeyPath) -> Void, +// copied: (_ keyPath: KeyPath) -> Void +// ) +} + +public extension MigrationMappingProvider { + +// func migrate(from oldObject: SourceType, to newObject: DestinationType, transaction: UnsafeDataTransaction) { +// +// +// } +// +// func forEachPropertyMapping(from oldObject: SourceType, to newObject: DestinationType, removed: (_ keyPath: KeyPath) -> Void, added: (_ keyPath: KeyPath) -> Void, transformed: (_ keyPath: KeyPath) -> Void) { +// +// let oldAttributes = oldObject.cs_toRaw().entity.attributesByName +// let newAttributes = newObject.cs_toRaw().entity.attributesByName +// let oldAttributeKeys = Set(oldAttributes.keys) +// let newAttributeKeys = Set(newAttributes.keys) +// for keyPath in +// } +} + +public extension MigrationMappingProvider { + +} + + +// MARK: - UnsafeMigrationProxyObject + +public final class UnsafeMigrationProxyObject { + + public subscript(kvcKey: KeyPath) -> CoreDataNativeType? { + + get { + + return self.rawObject.cs_accessValueForKVCKey(kvcKey) + } + set { + + self.rawObject.cs_setValue(newValue, forKVCKey: kvcKey) + } + } + + + // MARK: Internal + + internal init(_ rawObject: NSManagedObject) { + + self.rawObject = rawObject + } + + + // MARK: Private + + private let rawObject: NSManagedObject } diff --git a/Sources/ObjectiveC/NSManagedObject+ObjectiveC.swift b/Sources/ObjectiveC/NSManagedObject+ObjectiveC.swift index d695e65..73f7866 100644 --- a/Sources/ObjectiveC/NSManagedObject+ObjectiveC.swift +++ b/Sources/ObjectiveC/NSManagedObject+ObjectiveC.swift @@ -38,7 +38,7 @@ public extension NSManagedObject { - returns: the primitive value for the KVC key */ @objc - public func cs_accessValueForKVCKey(_ kvcKey: KeyPath) -> Any? { + public func cs_accessValueForKVCKey(_ kvcKey: KeyPath) -> CoreDataNativeType? { return self.getValue(forKvcKey: kvcKey) } @@ -50,7 +50,7 @@ public extension NSManagedObject { - parameter KVCKey: the KVC key */ @objc - public func cs_setValue(_ value: Any?, forKVCKey KVCKey: KeyPath) { + public func cs_setValue(_ value: CoreDataNativeType?, forKVCKey KVCKey: KeyPath) { self.setValue(value, forKvcKey: KVCKey) } diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index 715caf4..30d07b6 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -96,6 +96,14 @@ public final class DataStack: Equatable { return self.schemaHistory.currentModelVersion } + /** + Returns the `DataStack`'s model schema. + */ + public var modelSchema: DynamicSchema { + + return self.schemaHistory.schemaByVersion[self.schemaHistory.currentModelVersion]! + } + /** Returns the entity name-to-class type mapping from the `DataStack`'s model. */ diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift index 6a8cc12..64cfc63 100644 --- a/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift @@ -31,7 +31,7 @@ import Foundation public final class CoreStoreSchema: DynamicSchema { - public convenience init(modelVersion: String, entities: [DynamicEntity], versionLock: VersionLock? = nil) { + public convenience init(modelVersion: ModelVersion, entities: [DynamicEntity], versionLock: VersionLock? = nil) { self.init( modelVersion: modelVersion, @@ -40,7 +40,7 @@ public final class CoreStoreSchema: DynamicSchema { ) } - public required init(modelVersion: String, entitiesByConfiguration: [String: [DynamicEntity]], versionLock: VersionLock? = nil) { + public required init(modelVersion: ModelVersion, entitiesByConfiguration: [String: [DynamicEntity]], versionLock: VersionLock? = nil) { var actualEntitiesByConfiguration: [String: Set] = [:] for (configuration, entities) in entitiesByConfiguration { @@ -129,14 +129,20 @@ public final class CoreStoreSchema: DynamicSchema { internal init(_ entity: DynamicEntity) { - self.type = entity.type - self.entityName = entity.entityName + self.init( + type: entity.type, + entityName: entity.entityName, + isAbstract: entity.isAbstract, + versionHashModifier: entity.versionHashModifier + ) } - internal init(type: CoreStoreObject.Type, entityName: String) { + internal init(type: CoreStoreObject.Type, entityName: String, isAbstract: Bool, versionHashModifier: String?) { self.type = type self.entityName = entityName + self.isAbstract = isAbstract + self.versionHashModifier = versionHashModifier } @@ -146,6 +152,8 @@ public final class CoreStoreSchema: DynamicSchema { return lhs.type == rhs.type && lhs.entityName == rhs.entityName + && lhs.isAbstract == rhs.isAbstract + && lhs.versionHashModifier == rhs.versionHashModifier } // MARK: Hashable @@ -154,12 +162,16 @@ public final class CoreStoreSchema: DynamicSchema { return ObjectIdentifier(self.type).hashValue ^ self.entityName.hashValue + ^ self.isAbstract.hashValue + ^ (self.versionHashModifier ?? "").hashValue } // MARK: DynamicEntity internal let type: CoreStoreObject.Type internal let entityName: EntityName + internal let isAbstract: Bool + internal let versionHashModifier: String? } @@ -193,6 +205,7 @@ public final class CoreStoreSchema: DynamicSchema { let entityDescription = NSEntityDescription() entityDescription.anyEntity = entity entityDescription.name = entity.entityName + entityDescription.isAbstract = entity.isAbstract entityDescription.managedObjectClassName = NSStringFromClass(NSManagedObject.self) func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] { @@ -216,8 +229,8 @@ public final class CoreStoreSchema: DynamicSchema { case let relationship as RelationshipProtocol: let description = NSRelationshipDescription() description.name = relationship.keyPath - description.minCount = 0 - description.maxCount = relationship.isToMany ? 0 : 1 + description.minCount = relationship.minCount + description.maxCount = relationship.maxCount description.isOrdered = relationship.isOrdered description.deleteRule = relationship.deleteRule // TODO: versionHash, renamingIdentifier, etc diff --git a/Sources/Setup/Dynamic Models/Entity.swift b/Sources/Setup/Dynamic Models/Entity.swift index 91508fd..1dce650 100644 --- a/Sources/Setup/Dynamic Models/Entity.swift +++ b/Sources/Setup/Dynamic Models/Entity.swift @@ -34,6 +34,8 @@ public protocol DynamicEntity { var type: CoreStoreObject.Type { get } var entityName: EntityName { get } + var isAbstract: Bool { get } + var versionHashModifier: String? { get } } @@ -41,15 +43,17 @@ public protocol DynamicEntity { public struct Entity: DynamicEntity, Hashable { - public init(_ entityName: String) { + public init(_ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) { - self.init(O.self, entityName) + self.init(O.self, entityName, isAbstract: isAbstract, versionHashModifier: versionHashModifier) } - public init(_ type: O.Type, _ entityName: String) { + public init(_ type: O.Type, _ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) { self.type = type self.entityName = entityName + self.isAbstract = isAbstract + self.versionHashModifier = versionHashModifier } @@ -57,6 +61,8 @@ public struct Entity: DynamicEntity, Hashable { public let type: CoreStoreObject.Type public let entityName: EntityName + public let isAbstract: Bool + public let versionHashModifier: String? // MARK: Equatable @@ -65,6 +71,8 @@ public struct Entity: DynamicEntity, Hashable { return lhs.type == rhs.type && lhs.entityName == rhs.entityName + && lhs.isAbstract == rhs.isAbstract + && lhs.versionHashModifier == rhs.versionHashModifier } // MARK: Hashable @@ -72,5 +80,8 @@ public struct Entity: DynamicEntity, Hashable { public var hashValue: Int { return ObjectIdentifier(self.type).hashValue + ^ self.entityName.hashValue + ^ self.isAbstract.hashValue + ^ (self.versionHashModifier ?? "").hashValue } } diff --git a/Sources/Setup/Dynamic Models/Relationship.swift b/Sources/Setup/Dynamic Models/Relationship.swift index 7cba56f..9ed34b4 100644 --- a/Sources/Setup/Dynamic Models/Relationship.swift +++ b/Sources/Setup/Dynamic Models/Relationship.swift @@ -100,6 +100,10 @@ public enum RelationshipContainer { self.accessRawObject().isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) + CoreStore.assert( + self.accessRawObject().isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) self.accessRawObject() .setValue( newValue, @@ -117,6 +121,8 @@ public enum RelationshipContainer { internal let isToMany = false internal let isOrdered = false internal let deleteRule: NSDeleteRule + internal let minCount: Int = 0 + internal let maxCount: Int = 1 internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) internal var accessRawObject: () -> NSManagedObject = { @@ -157,24 +163,24 @@ public enum RelationshipContainer { relationship.value = relationship2.value } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { - self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) } // TODO: add subscripts, indexed operations for more performant single updates @@ -206,6 +212,10 @@ public enum RelationshipContainer { self.accessRawObject().isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) + CoreStore.assert( + self.accessRawObject().isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) self.accessRawObject() .setValue( newValue, @@ -224,6 +234,8 @@ public enum RelationshipContainer { internal let isOptional = true internal let isOrdered = true internal let deleteRule: NSDeleteRule + internal let minCount: Int + internal let maxCount: Int internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) internal var accessRawObject: () -> NSManagedObject = { @@ -234,11 +246,15 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: String, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule) { + private init(keyPath: String, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, minCount: Int, maxCount: Int) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue self.inverse = (D.self, inverseKeyPath) + + let range = (max(0, minCount) ... maxCount) + self.minCount = range.lowerBound + self.maxCount = range.upperBound } } @@ -269,24 +285,24 @@ public enum RelationshipContainer { relationship.value = Set(relationship2.value) } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { - self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) } // TODO: add subscripts, indexed operations for more performant single updates @@ -318,6 +334,10 @@ public enum RelationshipContainer { self.accessRawObject().isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) + CoreStore.assert( + self.accessRawObject().isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) self.accessRawObject() .setValue( newValue, @@ -336,6 +356,8 @@ public enum RelationshipContainer { internal let isOptional = true internal let isOrdered = true internal let deleteRule: NSDeleteRule + internal let minCount: Int + internal let maxCount: Int internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) internal var accessRawObject: () -> NSManagedObject = { @@ -346,11 +368,15 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule) { + private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue self.inverse = (D.self, inverseKeyPath) + + let range = (max(0, minCount) ... maxCount) + self.minCount = range.lowerBound + self.maxCount = range.upperBound } } @@ -386,4 +412,6 @@ internal protocol RelationshipProtocol: class { var deleteRule: NSDeleteRule { get } var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get } var accessRawObject: () -> NSManagedObject { get set } + var minCount: Int { get } + var maxCount: Int { get } } diff --git a/Sources/Setup/Dynamic Models/Value.swift b/Sources/Setup/Dynamic Models/Value.swift index 6c9b9d4..616427c 100644 --- a/Sources/Setup/Dynamic Models/Value.swift +++ b/Sources/Setup/Dynamic Models/Value.swift @@ -86,11 +86,15 @@ public enum ValueContainer { self.accessRawObject().isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) + CoreStore.assert( + self.accessRawObject().isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) self.accessRawObject() .setValue( newValue, forKvcKey: self.keyPath, - willSetValue: { $0.cs_toImportableNativeType() } + willSetValue: { ($0.cs_toImportableNativeType() as! CoreDataNativeType) } ) } } @@ -163,11 +167,15 @@ public enum ValueContainer { self.accessRawObject().isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) + CoreStore.assert( + self.accessRawObject().isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) self.accessRawObject() .setValue( newValue, forKvcKey: self.keyPath, - willSetValue: { $0?.cs_toImportableNativeType() } + willSetValue: { ($0?.cs_toImportableNativeType() as! CoreDataNativeType?) } ) } } From 274a54451a94a07d0f52057a7b5e0295fe1c92b6 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Fri, 21 Apr 2017 15:07:26 +0900 Subject: [PATCH 10/14] fix compiler errors --- Sources/Convenience/DynamicSchema+Convenience.swift | 2 +- Sources/Fetching and Querying/CoreStoreObject+Querying.swift | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/Convenience/DynamicSchema+Convenience.swift b/Sources/Convenience/DynamicSchema+Convenience.swift index 610e02c..65ea60b 100644 --- a/Sources/Convenience/DynamicSchema+Convenience.swift +++ b/Sources/Convenience/DynamicSchema+Convenience.swift @@ -91,7 +91,7 @@ public extension DynamicSchema { case .integer64AttributeType: valueType = Int64.self if let defaultValue = (attribute.defaultValue as! Int64.ImportableNativeType?).flatMap(Int64.cs_fromImportableNativeType), - defaultValue != Int54.cs_emptyValue() { + defaultValue != Int64.cs_emptyValue() { defaultString = ", default: \(defaultValue)" } diff --git a/Sources/Fetching and Querying/CoreStoreObject+Querying.swift b/Sources/Fetching and Querying/CoreStoreObject+Querying.swift index f2419bf..bb32925 100644 --- a/Sources/Fetching and Querying/CoreStoreObject+Querying.swift +++ b/Sources/Fetching and Querying/CoreStoreObject+Querying.swift @@ -31,19 +31,16 @@ import Foundation public extension DynamicObject where Self: CoreStoreObject { - @inline(__always) public static func keyPath(_ attribute: (Self) -> ValueContainer.Required) -> String { return attribute(self.meta).keyPath } - @inline(__always) public static func keyPath(_ attribute: (Self) -> ValueContainer.Optional) -> String { return attribute(self.meta).keyPath } - @inline(__always) public static func `where`(_ condition: (Self) -> Where) -> Where { return condition(self.meta) From e6aa72fb5fe06ea4f602a3b0caedbb12ae8552bd Mon Sep 17 00:00:00 2001 From: John Estropia Date: Fri, 21 Apr 2017 19:35:29 +0900 Subject: [PATCH 11/14] support transformable values --- CoreStore.xcodeproj/project.pbxproj | 10 + CoreStoreTests/DynamicModelTests.swift | 11 +- CoreStoreTests/SetupTests.swift | 2 - .../DynamicSchema+Convenience.swift | 42 ++- .../NSManagedObject+Convenience.swift | 16 +- .../CoreStoreObject+Querying.swift | 10 + .../NSManagedObject+DynamicModel.swift | 61 ++++ .../MigrationMappingProvider.swift | 2 +- .../NSManagedObject+ObjectiveC.swift | 4 +- .../Dynamic Models/CoreStoreObject.swift | 14 +- .../Setup/Dynamic Models/DynamicObject.swift | 15 +- .../Setup/Dynamic Models/Relationship.swift | 112 +++---- Sources/Setup/Dynamic Models/Value.swift | 313 ++++++++++++++++-- 13 files changed, 495 insertions(+), 117 deletions(-) create mode 100644 Sources/Internal/NSManagedObject+DynamicModel.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 4c2be98..79e1e7f 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -406,6 +406,10 @@ B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; }; B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; }; B58B22F51C93C1BA00521925 /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A53019C5C6DA005002A5 /* CoreStore.framework */; }; + B58D0C631EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */; }; + B58D0C641EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */; }; + B58D0C651EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */; }; + B58D0C661EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */; }; B596BBB21DD5A014001DCDD9 /* ConvenienceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */; }; B596BBB31DD5A014001DCDD9 /* ConvenienceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */; }; B596BBB41DD5A016001DCDD9 /* ConvenienceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */; }; @@ -761,6 +765,7 @@ B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestDataTestCase.swift; sourceTree = ""; }; B57D27C11D0BC20100539C58 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; B58085741CDF7F00004C2EEB /* SetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupTests.swift; sourceTree = ""; }; + B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+DynamicModel.swift"; sourceTree = ""; }; B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvenienceTests.swift; sourceTree = ""; }; B596BBB51DD5BC67001DCDD9 /* FetchableSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchableSource.swift; sourceTree = ""; }; B596BBBA1DD5C39F001DCDD9 /* QueryableSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryableSource.swift; sourceTree = ""; }; @@ -1366,6 +1371,7 @@ B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */, B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */, B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */, + B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */, B5E84F2C1AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift */, B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */, B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */, @@ -1802,6 +1808,7 @@ B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */, B5D33A011E96012400C880DE /* Relationship.swift in Sources */, B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */, + B58D0C631EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DB1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, B559CD491CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, B5ECDC2F1CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, @@ -1977,6 +1984,7 @@ 82BA18D81C4BBD7100A0916E /* WeakObject.swift in Sources */, B5D33A021E96012400C880DE /* Relationship.swift in Sources */, B559CD4B1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, + B58D0C641EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DC1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, B5ECDC311CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, 82BA18AF1C4BBD3100A0916E /* CoreStore+Transaction.swift in Sources */, @@ -2152,6 +2160,7 @@ B546F96C1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, B5D33A041E96012400C880DE /* Relationship.swift in Sources */, B52DD1C61BE1F94600949AFE /* NSManagedObjectContext+CoreStore.swift in Sources */, + B58D0C661EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DE1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, B5220E141D130614009BC71E /* DataStack+Observing.swift in Sources */, B52DD1A21BE1F92C00949AFE /* CoreStore+Transaction.swift in Sources */, @@ -2327,6 +2336,7 @@ B56321B61BD6521C006C9394 /* WeakObject.swift in Sources */, B5D33A031E96012400C880DE /* Relationship.swift in Sources */, B559CD4C1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, + B58D0C651EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DD1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, B5ECDC321CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, B56321AC1BD6521C006C9394 /* Functions.swift in Sources */, diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index bb4f994..e138e48 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -45,7 +45,13 @@ class Dog: Animal { class Person: CoreStoreObject { - let name = Value.Required("name") + let name = Value.Required( + "name", + customGetter: { (`self`, getValue) in + + return "Mr. \(getValue())" + } + ) let pet = Relationship.ToOne("pet", inverse: { $0.master }) } @@ -108,6 +114,9 @@ class DynamicModelTests: BaseTestDataTestCase { let person = transaction.create(Into()) XCTAssertNil(person.pet.value) + person.name .= "John" + XCTAssertEqual(person.name.value, "Mr. John") // Custom getter + person.pet .= dog XCTAssertEqual(person.pet.value, dog) XCTAssertEqual(person.pet.value?.master.value, person) diff --git a/CoreStoreTests/SetupTests.swift b/CoreStoreTests/SetupTests.swift index 44bfd22..2a7ee7d 100644 --- a/CoreStoreTests/SetupTests.swift +++ b/CoreStoreTests/SetupTests.swift @@ -213,7 +213,6 @@ class SetupTests: BaseTestDataTestCase { self.prepareTestDataForStack(stack) } XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path)) - XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal"))) XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm"))) return try NSPersistentStoreCoordinator.metadataForPersistentStore( @@ -334,7 +333,6 @@ class SetupTests: BaseTestDataTestCase { self.prepareTestDataForStack(stack) } XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path)) - XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal"))) XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm"))) return try NSPersistentStoreCoordinator.metadataForPersistentStore( diff --git a/Sources/Convenience/DynamicSchema+Convenience.swift b/Sources/Convenience/DynamicSchema+Convenience.swift index 65ea60b..fdcc6d6 100644 --- a/Sources/Convenience/DynamicSchema+Convenience.swift +++ b/Sources/Convenience/DynamicSchema+Convenience.swift @@ -62,13 +62,27 @@ public extension DynamicSchema { for (attributeName, attribute) in attributesByName { let containerType: String - if attribute.isOptional { + if attribute.attributeType == .transformableAttributeType { - containerType = "Value.Optional" + if attribute.isOptional { + + containerType = "Transformable.Optional" + } + else { + + containerType = "Transformable.Required" + } } else { - containerType = "Value.Required" + if attribute.isOptional { + + containerType = "Value.Optional" + } + else { + + containerType = "Value.Required" + } } let valueType: Any.Type var defaultString = "" @@ -151,11 +165,29 @@ public extension DynamicSchema { } defaultString = ", default: Data(bytes: [\(bytes.joined(separator: ", "))])" } + case .transformableAttributeType: + if let attributeValueClassName = attribute.attributeValueClassName { + + valueType = NSClassFromString(attributeValueClassName)! + } + else { + + valueType = (NSCoding & NSCopying).self + } + if let defaultValue = attribute.defaultValue { + + defaultString = ", default: /* \"\(defaultValue)\" */" + } + else if !attribute.isOptional { + + defaultString = ", default: /* required */" + } default: - fatalError("Unsupported attribute type: \(attribute.attributeType)") + fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)") } + let indexedString = attribute.isIndexed ? ", isIndexed: true" : "" let transientString = attribute.isTransient ? ", isTransient: true" : "" - output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(defaultString)\(transientString))\n") + output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(indexedString)\(defaultString)\(transientString))\n") } } diff --git a/Sources/Convenience/NSManagedObject+Convenience.swift b/Sources/Convenience/NSManagedObject+Convenience.swift index daf88b5..d3c273c 100644 --- a/Sources/Convenience/NSManagedObject+Convenience.swift +++ b/Sources/Convenience/NSManagedObject+Convenience.swift @@ -78,29 +78,29 @@ public extension NSManagedObject { } @nonobjc @inline(__always) - public func getValue(forKvcKey kvcKey: KeyPath) -> CoreDataNativeType? { + public func getValue(forKvcKey kvcKey: KeyPath) -> Any? { self.willAccessValue(forKey: kvcKey) defer { self.didAccessValue(forKey: kvcKey) } - return self.primitiveValue(forKey: kvcKey) as! CoreDataNativeType? + return self.primitiveValue(forKey: kvcKey) } @nonobjc @inline(__always) - public func getValue(forKvcKey kvcKey: KeyPath, didGetValue: (CoreDataNativeType?) throws -> T) rethrows -> T { + public func getValue(forKvcKey kvcKey: KeyPath, didGetValue: (Any?) throws -> T) rethrows -> T { self.willAccessValue(forKey: kvcKey) defer { self.didAccessValue(forKey: kvcKey) } - return try didGetValue(self.primitiveValue(forKey: kvcKey) as! CoreDataNativeType?) + return try didGetValue(self.primitiveValue(forKey: kvcKey)) } @nonobjc @inline(__always) - public func getValue(forKvcKey kvcKey: KeyPath, willGetValue: () throws -> Void, didGetValue: (CoreDataNativeType?) throws -> T) rethrows -> T { + public func getValue(forKvcKey kvcKey: KeyPath, willGetValue: () throws -> Void, didGetValue: (Any?) throws -> T) rethrows -> T { self.willAccessValue(forKey: kvcKey) defer { @@ -108,11 +108,11 @@ public extension NSManagedObject { self.didAccessValue(forKey: kvcKey) } try willGetValue() - return try didGetValue(self.primitiveValue(forKey: kvcKey) as! CoreDataNativeType?) + return try didGetValue(self.primitiveValue(forKey: kvcKey)) } @nonobjc @inline(__always) - public func setValue(_ value: CoreDataNativeType?, forKvcKey KVCKey: KeyPath) { + public func setValue(_ value: Any?, forKvcKey KVCKey: KeyPath) { self.willChangeValue(forKey: KVCKey) defer { @@ -123,7 +123,7 @@ public extension NSManagedObject { } @nonobjc @inline(__always) - public func setValue(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> CoreDataNativeType?) rethrows { + public func setValue(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> Any?) rethrows { self.willChangeValue(forKey: KVCKey) defer { diff --git a/Sources/Fetching and Querying/CoreStoreObject+Querying.swift b/Sources/Fetching and Querying/CoreStoreObject+Querying.swift index bb32925..419457a 100644 --- a/Sources/Fetching and Querying/CoreStoreObject+Querying.swift +++ b/Sources/Fetching and Querying/CoreStoreObject+Querying.swift @@ -45,6 +45,16 @@ public extension DynamicObject where Self: CoreStoreObject { return condition(self.meta) } + + public static func ascending(_ attribute: (Self) -> ValueContainer.Optional) -> OrderBy { + + return OrderBy(.ascending(attribute(self.meta).keyPath)) + } + + public static func descending(_ attribute: (Self) -> ValueContainer.Optional) -> OrderBy { + + return OrderBy(.descending(attribute(self.meta).keyPath)) + } } diff --git a/Sources/Internal/NSManagedObject+DynamicModel.swift b/Sources/Internal/NSManagedObject+DynamicModel.swift new file mode 100644 index 0000000..c385b50 --- /dev/null +++ b/Sources/Internal/NSManagedObject+DynamicModel.swift @@ -0,0 +1,61 @@ +// +// NSManagedObject+DynamicModel.swift +// CoreStore +// +// Copyright © 2017 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation +import CoreData + + +// MARK: - NSManagedObject + +internal extension NSManagedObject { + + @nonobjc + internal weak var coreStoreObject: CoreStoreObject? { + + get { + + return cs_getAssociatedObjectForKey( + &PropertyKeys.coreStoreObject, + inObject: self + ) + } + set { + + cs_setAssociatedWeakObject( + newValue, + forKey: &PropertyKeys.coreStoreObject, + inObject: self + ) + } + } + + + // MARK: Private + + private struct PropertyKeys { + + static var coreStoreObject: Void? + } +} diff --git a/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift b/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift index 52e1402..3f5f38a 100644 --- a/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift +++ b/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift @@ -81,7 +81,7 @@ public extension MigrationMappingProvider { public final class UnsafeMigrationProxyObject { - public subscript(kvcKey: KeyPath) -> CoreDataNativeType? { + public subscript(kvcKey: KeyPath) -> Any? { get { diff --git a/Sources/ObjectiveC/NSManagedObject+ObjectiveC.swift b/Sources/ObjectiveC/NSManagedObject+ObjectiveC.swift index 73f7866..d695e65 100644 --- a/Sources/ObjectiveC/NSManagedObject+ObjectiveC.swift +++ b/Sources/ObjectiveC/NSManagedObject+ObjectiveC.swift @@ -38,7 +38,7 @@ public extension NSManagedObject { - returns: the primitive value for the KVC key */ @objc - public func cs_accessValueForKVCKey(_ kvcKey: KeyPath) -> CoreDataNativeType? { + public func cs_accessValueForKVCKey(_ kvcKey: KeyPath) -> Any? { return self.getValue(forKvcKey: kvcKey) } @@ -50,7 +50,7 @@ public extension NSManagedObject { - parameter KVCKey: the KVC key */ @objc - public func cs_setValue(_ value: CoreDataNativeType?, forKVCKey KVCKey: KeyPath) { + public func cs_setValue(_ value: Any?, forKVCKey KVCKey: KeyPath) { self.setValue(value, forKvcKey: KVCKey) } diff --git a/Sources/Setup/Dynamic Models/CoreStoreObject.swift b/Sources/Setup/Dynamic Models/CoreStoreObject.swift index d640b3a..1dfcd9d 100644 --- a/Sources/Setup/Dynamic Models/CoreStoreObject.swift +++ b/Sources/Setup/Dynamic Models/CoreStoreObject.swift @@ -54,11 +54,11 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable { Do not call this directly. This is exposed as public only as a required initializer. - Important: subclasses that need a custom initializer should override both `init(_:)` and `init(asMeta:)`, and to call their corresponding super implementations. */ - public required init(_ object: NSManagedObject) { + public required init(rawObject: NSManagedObject) { self.isMeta = false - self.rawObject = object - self.initializeAttributes(Mirror(reflecting: self), { [unowned object] in object }) + self.rawObject = rawObject + self.initializeAttributes(Mirror(reflecting: self), { [unowned self] in self }) } /** @@ -105,18 +105,18 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable { // MARK: Private - private func initializeAttributes(_ mirror: Mirror, _ accessRawObject: @escaping () -> NSManagedObject) { + private func initializeAttributes(_ mirror: Mirror, _ parentObject: @escaping () -> CoreStoreObject) { - _ = mirror.superclassMirror.flatMap({ self.initializeAttributes($0, accessRawObject) }) + _ = mirror.superclassMirror.flatMap({ self.initializeAttributes($0, parentObject) }) for child in mirror.children { switch child.value { case let property as AttributeProtocol: - property.accessRawObject = accessRawObject + property.parentObject = parentObject case let property as RelationshipProtocol: - property.accessRawObject = accessRawObject + property.parentObject = parentObject default: continue diff --git a/Sources/Setup/Dynamic Models/DynamicObject.swift b/Sources/Setup/Dynamic Models/DynamicObject.swift index deb7841..4544073 100644 --- a/Sources/Setup/Dynamic Models/DynamicObject.swift +++ b/Sources/Setup/Dynamic Models/DynamicObject.swift @@ -85,12 +85,23 @@ extension CoreStoreObject { context.assign(object, to: store) } - return self.init(object) + return self.cs_fromRaw(object: object) } public class func cs_fromRaw(object: NSManagedObject) -> Self { - return self.init(object) + if let coreStoreObject = object.coreStoreObject { + + @inline(__always) + func forceCast(_ value: CoreStoreObject) -> T { + + return value as! T + } + return forceCast(coreStoreObject) + } + let coreStoreObject = self.init(rawObject: object) + object.coreStoreObject = coreStoreObject + return coreStoreObject } public func cs_toRaw() -> NSManagedObject { diff --git a/Sources/Setup/Dynamic Models/Relationship.swift b/Sources/Setup/Dynamic Models/Relationship.swift index 9ed34b4..9f1b5b3 100644 --- a/Sources/Setup/Dynamic Models/Relationship.swift +++ b/Sources/Setup/Dynamic Models/Relationship.swift @@ -84,32 +84,32 @@ public enum RelationshipContainer { get { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - return self.accessRawObject() - .getValue( - forKvcKey: self.keyPath, - didGetValue: { $0.flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) } - ) + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { $0.flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) } + ) } set { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) CoreStore.assert( - self.accessRawObject().isEditableInContext() == true, + object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) - self.accessRawObject() - .setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { $0?.rawObject } - ) + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { $0?.rawObject } + ) } } @@ -125,7 +125,7 @@ public enum RelationshipContainer { internal let maxCount: Int = 1 internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) - internal var accessRawObject: () -> NSManagedObject = { + 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.") } @@ -189,39 +189,39 @@ public enum RelationshipContainer { get { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - return self.accessRawObject() - .getValue( - forKvcKey: self.keyPath, - didGetValue: { + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { + + guard let orderedSet = $0 as! NSOrderedSet? else { - guard let orderedSet = $0 as! NSOrderedSet? else { - - return [] - } - return orderedSet.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) + return [] } - ) + return orderedSet.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) + } + ) } set { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) CoreStore.assert( - self.accessRawObject().isEditableInContext() == true, + object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) - self.accessRawObject() - .setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { NSOrderedSet(array: $0.map({ $0.rawObject! })) } - ) + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { NSOrderedSet(array: $0.map({ $0.rawObject! })) } + ) } } @@ -238,7 +238,7 @@ public enum RelationshipContainer { internal let maxCount: Int internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) - internal var accessRawObject: () -> NSManagedObject = { + 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.") } @@ -311,39 +311,39 @@ public enum RelationshipContainer { get { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - return self.accessRawObject() - .getValue( - forKvcKey: self.keyPath, - didGetValue: { + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { + + guard let set = $0 as! NSSet? else { - guard let set = $0 as! NSSet? else { - - return [] - } - return Set(set.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })) + return [] } - ) + return Set(set.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })) + } + ) } set { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) CoreStore.assert( - self.accessRawObject().isEditableInContext() == true, + object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) - self.accessRawObject() - .setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { NSSet(array: $0.map({ $0.rawObject! })) } - ) + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { NSSet(array: $0.map({ $0.rawObject! })) } + ) } } @@ -360,7 +360,7 @@ public enum RelationshipContainer { internal let maxCount: Int internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) - internal var accessRawObject: () -> NSManagedObject = { + 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.") } @@ -411,7 +411,7 @@ internal protocol RelationshipProtocol: class { var isOrdered: Bool { get } var deleteRule: NSDeleteRule { get } var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get } - var accessRawObject: () -> NSManagedObject { get set } + var parentObject: () -> CoreStoreObject { get set } var minCount: Int { get } var maxCount: Int { get } } diff --git a/Sources/Setup/Dynamic Models/Value.swift b/Sources/Setup/Dynamic Models/Value.swift index 616427c..5394560 100644 --- a/Sources/Setup/Dynamic Models/Value.swift +++ b/Sources/Setup/Dynamic Models/Value.swift @@ -37,6 +37,7 @@ infix operator .= : AssignmentPrecedence public extension DynamicObject where Self: CoreStoreObject { public typealias Value = ValueContainer + public typealias Transformable = TransformableContainer } @@ -58,44 +59,59 @@ public enum ValueContainer { attribute.value = attribute2.value } - public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), isIndexed: Bool = false, isTransient: Bool = false) { + public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), isIndexed: Bool = false, isTransient: Bool = false, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) { self.keyPath = keyPath self.isIndexed = isIndexed self.isTransient = isTransient self.defaultValue = `default`.cs_toImportableNativeType() + self.customGetter = customGetter + self.customSetter = customSetter } public var value: V { get { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - return self.accessRawObject() - .getValue( - forKvcKey: self.keyPath, - didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType)! } - ) + return self.customGetter( + object, + { () -> V in + + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType)! } + ) + } + ) } set { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) CoreStore.assert( - self.accessRawObject().isEditableInContext() == true, + object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) - self.accessRawObject() - .setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { ($0.cs_toImportableNativeType() as! CoreDataNativeType) } - ) + self.customSetter( + object, + { (newValue: V) -> Void in + + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { $0.cs_toImportableNativeType() } + ) + }, + newValue + ) } } @@ -114,10 +130,16 @@ public enum ValueContainer { internal let isTransient: Bool internal let defaultValue: Any? - internal var accessRawObject: () -> NSManagedObject = { + 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.") } + + + // MARK: Private + + private let customGetter: (_ `self`: O, _ getValue: () -> V) -> V + private let customSetter: (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void } @@ -140,43 +162,58 @@ public enum ValueContainer { attribute.value = attribute2.value } - public init(_ keyPath: KeyPath, `default`: V? = nil, isTransient: Bool = false) { + public init(_ keyPath: KeyPath, `default`: V? = nil, isTransient: Bool = false, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) { self.keyPath = keyPath self.isTransient = isTransient self.defaultValue = `default`?.cs_toImportableNativeType() + self.customGetter = customGetter + self.customSetter = customSetter } public var value: V? { get { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - return self.accessRawObject() - .getValue( - forKvcKey: self.keyPath, - didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) } - ) + return self.customGetter( + object, + { () -> V? in + + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) } + ) + } + ) } set { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) CoreStore.assert( - self.accessRawObject().isEditableInContext() == true, + object.rawObject!.isEditableInContext() == true, "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." ) - self.accessRawObject() - .setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { ($0?.cs_toImportableNativeType() as! CoreDataNativeType?) } - ) + self.customSetter( + object, + { (newValue: V?) -> Void in + + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { $0?.cs_toImportableNativeType() } + ) + }, + newValue + ) } } @@ -194,10 +231,220 @@ public enum ValueContainer { internal let isTransient: Bool internal let defaultValue: Any? - internal var accessRawObject: () -> NSManagedObject = { + 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.") } + + + // MARK: Private + + private let customGetter: (_ `self`: O, _ getValue: () -> V?) -> V? + private let customSetter: (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void + } +} + + +// MARK: - TransformableContainer + +public enum TransformableContainer { + + // MARK: - Required + + public final class Required: AttributeProtocol { + + public static func .= (_ attribute: TransformableContainer.Required, _ value: V) { + + attribute.value = value + } + + public static func .= (_ attribute: TransformableContainer.Required, _ attribute2: TransformableContainer.Required) { + + attribute.value = attribute2.value + } + + public init(_ keyPath: KeyPath, `default`: V, isIndexed: Bool = false, isTransient: Bool = false, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) { + + self.keyPath = keyPath + self.defaultValue = `default` + self.isIndexed = isIndexed + self.isTransient = isTransient + self.customGetter = customGetter + self.customSetter = customSetter + } + + public var value: V { + + 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 } + ) + } + ) + } + 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 + ) + } + } + + + // MARK: AttributeProtocol + + internal static var attributeType: NSAttributeType { + + return .transformableAttributeType + } + + public let keyPath: KeyPath + + internal let isOptional = false + internal let isIndexed: Bool + internal let isTransient: Bool + internal let defaultValue: Any? + + 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.") + } + + + // MARK: Private + + private let customGetter: (_ `self`: O, _ getValue: () -> V) -> V + private let customSetter: (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void + } + + + // MARK: - Optional + + public final class Optional: AttributeProtocol { + + public static func .= (_ attribute: TransformableContainer.Optional, _ value: V) { + + attribute.value = value + } + + public static func .= (_ attribute: TransformableContainer.Optional, _ attribute2: TransformableContainer.Optional) { + + attribute.value = attribute2.value + } + + public static func .= (_ attribute: TransformableContainer.Optional, _ attribute2: TransformableContainer.Required) { + + attribute.value = attribute2.value + } + + public init(_ keyPath: KeyPath, `default`: V? = nil, isIndexed: Bool = false, isTransient: Bool = false, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) { + + self.keyPath = keyPath + self.defaultValue = `default` + self.isIndexed = isIndexed + self.isTransient = isTransient + self.customGetter = customGetter + self.customSetter = customSetter + } + + public var value: V? { + + 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? } + ) + } + ) + } + 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 + ) + } + } + + + // MARK: AttributeProtocol + + internal static var attributeType: NSAttributeType { + + return .transformableAttributeType + } + + public let keyPath: KeyPath + + internal let isOptional = false + internal let isIndexed: Bool + internal let isTransient: Bool + internal let defaultValue: Any? + + 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.") + } + + + // MARK: Private + + private let customGetter: (_ `self`: O, _ getValue: () -> V?) -> V? + private let customSetter: (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void } } @@ -213,5 +460,5 @@ internal protocol AttributeProtocol: class { var isIndexed: Bool { get } var isTransient: Bool { get } var defaultValue: Any? { get } - var accessRawObject: () -> NSManagedObject { get set } + var parentObject: () -> CoreStoreObject { get set } } From 9dc4331b264236d80d7f015213bb71e4b958f484 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Fri, 21 Apr 2017 21:21:47 +0900 Subject: [PATCH 12/14] oops --- Sources/Internal/EntityIdentifier.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Internal/EntityIdentifier.swift b/Sources/Internal/EntityIdentifier.swift index a12b0b0..6004139 100644 --- a/Sources/Internal/EntityIdentifier.swift +++ b/Sources/Internal/EntityIdentifier.swift @@ -48,7 +48,7 @@ internal struct EntityIdentifier: Hashable { internal init(_ type: NSManagedObject.Type) { self.category = .coreData - self.interfacedClassName = String(reflecting: type) + self.interfacedClassName = NSStringFromClass(type) } internal init(_ type: CoreStoreObject.Type) { From 53ab140341f412b3cf0efd09db32d850ea2a60ea Mon Sep 17 00:00:00 2001 From: John Estropia Date: Mon, 24 Apr 2017 11:57:47 +0900 Subject: [PATCH 13/14] Improve refetch method (may fix #118) --- .../ListObserverDemoViewController.swift | 5 +- .../FetchedResultsControllerDelegate.swift | 8 +++ Sources/Observing/ListMonitor.swift | 68 +++++++++++++------ 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift index 0b2d310..27f5fab 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift @@ -43,7 +43,10 @@ private struct Static { didSet { - self.palettes.refetch(self.filter.whereClause()) + self.palettes.refetch( + self.filter.whereClause(), + OrderBy(.ascending(#keyPath(Palette.hue))) + ) } } diff --git a/Sources/Internal/FetchedResultsControllerDelegate.swift b/Sources/Internal/FetchedResultsControllerDelegate.swift index 395fdfb..dc1d251 100644 --- a/Sources/Internal/FetchedResultsControllerDelegate.swift +++ b/Sources/Internal/FetchedResultsControllerDelegate.swift @@ -54,6 +54,9 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult @nonobjc internal var enabled = true + @nonobjc + internal let taskGroup = DispatchGroup() + @nonobjc internal weak var handler: FetchedResultsControllerHandler? @@ -78,6 +81,7 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult @objc dynamic func controllerWillChangeContent(_ controller: NSFetchedResultsController) { + self.taskGroup.enter() guard self.enabled else { return @@ -92,6 +96,10 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult @objc dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + defer { + + self.taskGroup.leave() + } guard self.enabled else { return diff --git a/Sources/Observing/ListMonitor.swift b/Sources/Observing/ListMonitor.swift index 505c5fe..674d669 100644 --- a/Sources/Observing/ListMonitor.swift +++ b/Sources/Observing/ListMonitor.swift @@ -555,7 +555,7 @@ public final class ListMonitor: Hashable { `refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes. - - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Note that only specified clauses will be changed; unspecified clauses will use previous values. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`. */ public func refetch(_ fetchClauses: FetchClause...) { @@ -567,7 +567,7 @@ public final class ListMonitor: Hashable { `refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes. - - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Note that only specified clauses will be changed; unspecified clauses will use previous values. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`. */ public func refetch(_ fetchClauses: [FetchClause]) { @@ -946,8 +946,12 @@ public final class ListMonitor: Hashable { return } - self.fetchedResultsControllerDelegate.enabled = false - self.applyFetchClauses(self.fetchedResultsController.fetchRequest) + let (newFetchedResultsController, newFetchedResultsControllerDelegate) = ListMonitor.recreateFetchedResultsController( + context: self.fetchedResultsController.managedObjectContext, + from: self.from, + sectionBy: self.sectionBy, + applyFetchClauses: self.applyFetchClauses + ) self.transactionQueue.async { [weak self] in @@ -956,16 +960,21 @@ public final class ListMonitor: Hashable { return } - try! self.fetchedResultsController.performFetchFromSpecifiedStores() - - DispatchQueue.main.async { [weak self] () -> Void in + try! newFetchedResultsController.performFetchFromSpecifiedStores() + self.fetchedResultsControllerDelegate.taskGroup.notify(queue: .main) { + + self.fetchedResultsControllerDelegate.enabled = false + } + newFetchedResultsControllerDelegate.taskGroup.notify(queue: .main) { [weak self] () -> Void in guard let `self` = self else { return } - self.fetchedResultsControllerDelegate.enabled = true + (self.fetchedResultsController, self.fetchedResultsControllerDelegate) = (newFetchedResultsController, newFetchedResultsControllerDelegate) + newFetchedResultsControllerDelegate.handler = self + self.isPendingRefetch = false NotificationCenter.default.post( @@ -986,7 +995,7 @@ public final class ListMonitor: Hashable { // MARK: Private - fileprivate let fetchedResultsController: CoreStoreFetchedResultsController + fileprivate var fetchedResultsController: CoreStoreFetchedResultsController fileprivate let taskGroup = DispatchGroup() fileprivate let sectionIndexTransformer: (_ sectionName: KeyPath?) -> String? @@ -1005,7 +1014,7 @@ public final class ListMonitor: Hashable { private var didInsertSectionKey: Void? private var didDeleteSectionKey: Void? - private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate + private var fetchedResultsControllerDelegate: FetchedResultsControllerDelegate private var observerForWillChangePersistentStore: NotificationObserver! private var observerForDidChangePersistentStore: NotificationObserver! private let transactionQueue: DispatchQueue @@ -1032,9 +1041,7 @@ public final class ListMonitor: Hashable { } } - private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: ((ListMonitor) -> Void)?) { - - self.isSectioned = (sectionBy != nil) + private static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void) -> (controller: CoreStoreFetchedResultsController, delegate: FetchedResultsControllerDelegate) { let fetchRequest = CoreStoreFetchRequest() fetchRequest.fetchLimit = 0 @@ -1052,9 +1059,25 @@ public final class ListMonitor: Hashable { ) let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate() + fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController - self.fetchedResultsController = fetchedResultsController - self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate + return (fetchedResultsController, fetchedResultsControllerDelegate) + } + + private let from: From + private let sectionBy: SectionBy? + + private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: ((ListMonitor) -> Void)?) { + + self.isSectioned = (sectionBy != nil) + self.from = from + self.sectionBy = sectionBy + (self.fetchedResultsController, self.fetchedResultsControllerDelegate) = ListMonitor.recreateFetchedResultsController( + context: context, + from: from, + sectionBy: sectionBy, + applyFetchClauses: applyFetchClauses + ) if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer { @@ -1066,9 +1089,7 @@ public final class ListMonitor: Hashable { } self.transactionQueue = transactionQueue self.applyFetchClauses = applyFetchClauses - - fetchedResultsControllerDelegate.handler = self - fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController + self.fetchedResultsControllerDelegate.handler = self guard let coordinator = context.parentStack?.coordinator else { @@ -1129,7 +1150,7 @@ public final class ListMonitor: Hashable { transactionQueue.async { - try! fetchedResultsController.performFetchFromSpecifiedStores() + try! self.fetchedResultsController.performFetchFromSpecifiedStores() self.taskGroup.notify(queue: .main) { createAsynchronously(self) @@ -1138,7 +1159,7 @@ public final class ListMonitor: Hashable { } else { - try! fetchedResultsController.performFetchFromSpecifiedStores() + try! self.fetchedResultsController.performFetchFromSpecifiedStores() } } } @@ -1326,12 +1347,15 @@ extension ListMonitor: FetchedResultsControllerHandler { } internal func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - + + defer { + + self.taskGroup.leave() + } NotificationCenter.default.post( name: Notification.Name.listMonitorDidChangeList, object: self ) - self.taskGroup.leave() } internal func controller(_ controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? { From 54c81d23f5ba8eb83c3acdcb792a1f9271dd1429 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 25 Apr 2017 18:08:43 +0900 Subject: [PATCH 14/14] WIP: dynamic migrations --- .../DynamicSchema+Convenience.swift | 10 +- .../MigrationMappingProvider.swift | 101 +++++++++++------- Sources/Setup/DataStack.swift | 2 +- .../Dynamic Schema/CoreStoreSchema.swift | 16 +-- Sources/Setup/Dynamic Models/Entity.swift | 6 +- .../Setup/Dynamic Models/Relationship.swift | 68 +++++++----- Sources/Setup/Dynamic Models/Value.swift | 26 ++++- 7 files changed, 147 insertions(+), 82 deletions(-) diff --git a/Sources/Convenience/DynamicSchema+Convenience.swift b/Sources/Convenience/DynamicSchema+Convenience.swift index fdcc6d6..e7f4b30 100644 --- a/Sources/Convenience/DynamicSchema+Convenience.swift +++ b/Sources/Convenience/DynamicSchema+Convenience.swift @@ -187,7 +187,11 @@ public extension DynamicSchema { } let indexedString = attribute.isIndexed ? ", isIndexed: true" : "" let transientString = attribute.isTransient ? ", isTransient: true" : "" - output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(indexedString)\(defaultString)\(transientString))\n") + // TODO: escape strings + let versionHashModifierString = attribute.versionHashModifier.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? "" + // TODO: escape strings + let renamingIdentifierString = attribute.renamingIdentifier.flatMap({ ", renamingIdentifier: \"\($0)\"" }) ?? "" + output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(indexedString)\(defaultString)\(transientString)\(versionHashModifierString)\(renamingIdentifierString))\n") } } @@ -251,7 +255,9 @@ public extension DynamicSchema { fatalError("Unsupported delete rule \((relationship.deleteRule)) for relationship \"\(relationshipQualifier)\"") } } - output.append(" let \(relationshipName) = \(containerType)<\(relationship.destinationEntity!.name!)>(\"\(relationshipName)\"\(inverseString)\(deleteRuleString)\(minCountString)\(maxCountString))\n") + let versionHashModifierString = relationship.versionHashModifier.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? "" + let renamingIdentifierString = relationship.renamingIdentifier.flatMap({ ", renamingIdentifier: \"\($0)\"" }) ?? "" + output.append(" let \(relationshipName) = \(containerType)<\(relationship.destinationEntity!.name!)>(\"\(relationshipName)\"\(inverseString)\(deleteRuleString)\(minCountString)\(maxCountString)\(versionHashModifierString)\(renamingIdentifierString))\n") } } } diff --git a/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift b/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift index 3f5f38a..fd6b271 100644 --- a/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift +++ b/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift @@ -27,53 +27,78 @@ import CoreData import Foundation -// MARK: - MigrationMappingProvider +// MARK: - SchemaMappingProvider -public protocol MigrationMappingProvider { +public protocol SchemaMappingProvider { - associatedtype SourceSchema: DynamicSchema - associatedtype DestinationSchema: DynamicSchema + var sourceSchema: DynamicSchema { get } + var destinationSchema: DynamicSchema { get } - var sourceSchema: SourceSchema { get } - var destinationSchema: DestinationSchema { get } - - init(source: SourceSchema, destination: DestinationSchema) - -// func migrate( -// from oldObject: SourceType, -// to newObject: DestinationType, -// transaction: UnsafeDataTransaction -// ) -// -// func forEachPropertyMapping( -// from oldObject: SourceType, -// to newObject: DestinationType, -// removed: (_ keyPath: KeyPath) -> Void, -// added: (_ keyPath: KeyPath) -> Void, -// transformed: (_ keyPath: KeyPath) -> Void, -// copied: (_ keyPath: KeyPath) -> Void -// ) + func createMappingModel() throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) } -public extension MigrationMappingProvider { +public protocol EntityMappingProvider { -// func migrate(from oldObject: SourceType, to newObject: DestinationType, transaction: UnsafeDataTransaction) { -// -// -// } -// -// func forEachPropertyMapping(from oldObject: SourceType, to newObject: DestinationType, removed: (_ keyPath: KeyPath) -> Void, added: (_ keyPath: KeyPath) -> Void, transformed: (_ keyPath: KeyPath) -> Void) { -// -// let oldAttributes = oldObject.cs_toRaw().entity.attributesByName -// let newAttributes = newObject.cs_toRaw().entity.attributesByName -// let oldAttributeKeys = Set(oldAttributes.keys) -// let newAttributeKeys = Set(newAttributes.keys) -// for keyPath in -// } + var source: (schema: DynamicSchema, entity: DynamicEntity) { get } + var destination: (schema: DynamicSchema, entity: DynamicEntity) { get } + + func createEntityMapping() -> NSEntityMapping } -public extension MigrationMappingProvider { + +// MARK: - XcodeMappingModelProvider + +open class XcodeMappingModelProvider: SchemaMappingProvider { + private let mappingModelBundles: [Bundle] + + public required init(source: SourceSchema, destination: DestinationSchema, mappingModelBundles: [Bundle] = Bundle.allBundles) { + + self.sourceSchema = source + self.destinationSchema = destination + self.mappingModelBundles = mappingModelBundles + } + + + // MARK: SchemaMappingProvider + + public typealias SourceSchema = S + public typealias DestinationSchema = D + + public let sourceSchema: SourceSchema + public let destinationSchema: DestinationSchema + + public func createMappingModel() throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) { + + let sourceModel = self.sourceSchema.rawModel() + let destinationModel = self.destinationSchema.rawModel() + + if let mappingModel = NSMappingModel( + from: self.mappingModelBundles, + forSourceModel: sourceModel, + destinationModel: destinationModel) { + + return ( + mappingModel, + .heavyweight( + sourceVersion: self.sourceSchema.modelVersion, + destinationVersion: self.destinationSchema.modelVersion + ) + ) + } + + let mappingModel = try NSMappingModel.inferredMappingModel( + forSourceModel: sourceModel, + destinationModel: destinationModel + ) + return ( + mappingModel, + .lightweight( + sourceVersion: self.sourceSchema.modelVersion, + destinationVersion: self.destinationSchema.modelVersion + ) + ) + } } diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index 30d07b6..7c926ed 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -149,7 +149,7 @@ public final class DataStack: Equatable { let actualType = anyEntity.type if (actualType as AnyClass).isSubclass(of: type) { - entityTypesByName[entityDescription.name!] = actualType + entityTypesByName[entityDescription.name!] = (actualType as! CoreStoreObject.Type) } } } diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift index 64cfc63..869c52e 100644 --- a/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift @@ -137,7 +137,7 @@ public final class CoreStoreSchema: DynamicSchema { ) } - internal init(type: CoreStoreObject.Type, entityName: String, isAbstract: Bool, versionHashModifier: String?) { + internal init(type: DynamicObject.Type, entityName: String, isAbstract: Bool, versionHashModifier: String?) { self.type = type self.entityName = entityName @@ -168,7 +168,7 @@ public final class CoreStoreSchema: DynamicSchema { // MARK: DynamicEntity - internal let type: CoreStoreObject.Type + internal let type: DynamicObject.Type internal let entityName: EntityName internal let isAbstract: Bool internal let versionHashModifier: String? @@ -223,7 +223,8 @@ public final class CoreStoreSchema: DynamicSchema { description.isIndexed = attribute.isIndexed description.defaultValue = attribute.defaultValue description.isTransient = attribute.isTransient - // TODO: versionHash, renamingIdentifier, etc + description.versionHashModifier = attribute.versionHashModifier + description.renamingIdentifier = attribute.renamingIdentifier propertyDescriptions.append(description) case let relationship as RelationshipProtocol: @@ -233,7 +234,8 @@ public final class CoreStoreSchema: DynamicSchema { description.maxCount = relationship.maxCount description.isOrdered = relationship.isOrdered description.deleteRule = relationship.deleteRule - // TODO: versionHash, renamingIdentifier, etc + description.versionHashModifier = relationship.versionHashModifier + description.renamingIdentifier = relationship.renamingIdentifier propertyDescriptions.append(description) default: @@ -243,7 +245,7 @@ public final class CoreStoreSchema: DynamicSchema { return propertyDescriptions } - entityDescription.properties = createProperties(for: entity.type) + entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type) return entityDescription } @@ -293,7 +295,7 @@ public final class CoreStoreSchema: DynamicSchema { for (entity, entityDescription) in entityDescriptionsByEntity { let relationshipsByName = relationshipsByNameByEntity[entity]! - for child in Mirror(reflecting: entity.type.meta).children { + for child in Mirror(reflecting: (entity.type as! CoreStoreObject.Type).meta).children { switch child.value { @@ -360,7 +362,7 @@ public final class CoreStoreSchema: DynamicSchema { for (entity, entityDescription) in entityDescriptionsByEntity { connectBaseEntity( - mirror: Mirror(reflecting: entity.type.meta), + mirror: Mirror(reflecting: (entity.type as! CoreStoreObject.Type).meta), entityDescription: entityDescription ) } diff --git a/Sources/Setup/Dynamic Models/Entity.swift b/Sources/Setup/Dynamic Models/Entity.swift index 1dce650..03fe560 100644 --- a/Sources/Setup/Dynamic Models/Entity.swift +++ b/Sources/Setup/Dynamic Models/Entity.swift @@ -32,7 +32,7 @@ import ObjectiveC public protocol DynamicEntity { - var type: CoreStoreObject.Type { get } + var type: DynamicObject.Type { get } var entityName: EntityName { get } var isAbstract: Bool { get } var versionHashModifier: String? { get } @@ -41,7 +41,7 @@ public protocol DynamicEntity { // MARK: Entity -public struct Entity: DynamicEntity, Hashable { +public struct Entity: DynamicEntity, Hashable { public init(_ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) { @@ -59,7 +59,7 @@ public struct Entity: DynamicEntity, Hashable { // MARK: DynamicEntity - public let type: CoreStoreObject.Type + public let type: DynamicObject.Type public let entityName: EntityName public let isAbstract: Bool public let versionHashModifier: String? diff --git a/Sources/Setup/Dynamic Models/Relationship.swift b/Sources/Setup/Dynamic Models/Relationship.swift index 9f1b5b3..5071194 100644 --- a/Sources/Setup/Dynamic Models/Relationship.swift +++ b/Sources/Setup/Dynamic Models/Relationship.swift @@ -60,24 +60,24 @@ public enum RelationshipContainer { relationship.value = relationship2.value } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } public var value: D? { @@ -124,6 +124,8 @@ public enum RelationshipContainer { internal let minCount: Int = 0 internal let maxCount: Int = 1 internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) + internal let versionHashModifier: String? + internal let renamingIdentifier: String? internal var parentObject: () -> CoreStoreObject = { @@ -133,11 +135,13 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule) { + private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue self.inverse = (D.self, inverseKeyPath) + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier } } @@ -163,24 +167,24 @@ public enum RelationshipContainer { relationship.value = relationship2.value } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { + public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) + self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { + 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) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { + 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) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { + 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) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } // TODO: add subscripts, indexed operations for more performant single updates @@ -237,6 +241,8 @@ public enum RelationshipContainer { internal let minCount: Int internal let maxCount: Int internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) + internal let versionHashModifier: String? + internal let renamingIdentifier: String? internal var parentObject: () -> CoreStoreObject = { @@ -246,11 +252,13 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: String, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, minCount: Int, maxCount: Int) { + private init(keyPath: String, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue self.inverse = (D.self, inverseKeyPath) + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier let range = (max(0, minCount) ... maxCount) self.minCount = range.lowerBound @@ -285,24 +293,24 @@ public enum RelationshipContainer { relationship.value = Set(relationship2.value) } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { + public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) + self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { + 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) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { + 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) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0) { + 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) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } // TODO: add subscripts, indexed operations for more performant single updates @@ -359,6 +367,8 @@ public enum RelationshipContainer { internal let minCount: Int internal let maxCount: Int internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) + internal let versionHashModifier: String? + internal let renamingIdentifier: String? internal var parentObject: () -> CoreStoreObject = { @@ -368,11 +378,13 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int) { + private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue self.inverse = (D.self, inverseKeyPath) + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier let range = (max(0, minCount) ... maxCount) self.minCount = range.lowerBound @@ -412,6 +424,8 @@ internal protocol RelationshipProtocol: class { var deleteRule: NSDeleteRule { get } var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get } var parentObject: () -> CoreStoreObject { get set } + var versionHashModifier: String? { get } + var renamingIdentifier: String? { get } var minCount: Int { get } var maxCount: Int { get } } diff --git a/Sources/Setup/Dynamic Models/Value.swift b/Sources/Setup/Dynamic Models/Value.swift index 5394560..d8cd2da 100644 --- a/Sources/Setup/Dynamic Models/Value.swift +++ b/Sources/Setup/Dynamic Models/Value.swift @@ -59,12 +59,14 @@ public enum ValueContainer { attribute.value = attribute2.value } - public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), isIndexed: Bool = false, isTransient: Bool = false, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) { + public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), 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: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) { self.keyPath = keyPath self.isIndexed = isIndexed self.isTransient = isTransient self.defaultValue = `default`.cs_toImportableNativeType() + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter self.customSetter = customSetter } @@ -129,6 +131,8 @@ public enum ValueContainer { internal let isIndexed: Bool internal let isTransient: Bool internal let defaultValue: Any? + internal let versionHashModifier: String? + internal let renamingIdentifier: String? internal var parentObject: () -> CoreStoreObject = { @@ -162,11 +166,13 @@ public enum ValueContainer { attribute.value = attribute2.value } - public init(_ keyPath: KeyPath, `default`: V? = nil, isTransient: Bool = false, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) { + public init(_ keyPath: KeyPath, `default`: V? = nil, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) { self.keyPath = keyPath self.isTransient = isTransient self.defaultValue = `default`?.cs_toImportableNativeType() + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter self.customSetter = customSetter } @@ -230,6 +236,8 @@ public enum ValueContainer { internal let isIndexed = false internal let isTransient: Bool internal let defaultValue: Any? + internal let versionHashModifier: String? + internal let renamingIdentifier: String? internal var parentObject: () -> CoreStoreObject = { @@ -263,12 +271,14 @@ public enum TransformableContainer { attribute.value = attribute2.value } - public init(_ keyPath: KeyPath, `default`: V, isIndexed: Bool = false, isTransient: Bool = false, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V) -> Void, _ newValue: 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: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) { self.keyPath = keyPath self.defaultValue = `default` self.isIndexed = isIndexed self.isTransient = isTransient + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter self.customSetter = customSetter } @@ -332,6 +342,8 @@ public enum TransformableContainer { internal let isIndexed: Bool internal let isTransient: Bool internal let defaultValue: Any? + internal let versionHashModifier: String? + internal let renamingIdentifier: String? internal var parentObject: () -> CoreStoreObject = { @@ -365,12 +377,14 @@ public enum TransformableContainer { attribute.value = attribute2.value } - public init(_ keyPath: KeyPath, `default`: V? = nil, isIndexed: Bool = false, isTransient: Bool = false, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: 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: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) { self.keyPath = keyPath self.defaultValue = `default` self.isIndexed = isIndexed self.isTransient = isTransient + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier self.customGetter = customGetter self.customSetter = customSetter } @@ -434,6 +448,8 @@ public enum TransformableContainer { internal let isIndexed: Bool internal let isTransient: Bool internal let defaultValue: Any? + internal let versionHashModifier: String? + internal let renamingIdentifier: String? internal var parentObject: () -> CoreStoreObject = { @@ -460,5 +476,7 @@ internal protocol AttributeProtocol: class { var isIndexed: Bool { get } var isTransient: Bool { get } var defaultValue: Any? { get } + var versionHashModifier: String? { get } + var renamingIdentifier: String? { get } var parentObject: () -> CoreStoreObject { get set } }