From 4619fbbec3149570d3c70362be5aeeb892b90311 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 16 Oct 2019 08:22:03 +0900 Subject: [PATCH] WIP: ObjectRepresentable --- Sources/LiveObject.swift | 54 +++++++++++----- Sources/ObjectMonitor.swift | 100 ++++++++++++++++++----------- Sources/ObjectRepresentation.swift | 55 ++++++++++++---- Sources/ObjectSnapshot.swift | 52 +++++++++++++-- 4 files changed, 190 insertions(+), 71 deletions(-) diff --git a/Sources/LiveObject.swift b/Sources/LiveObject.swift index a21cb8b..80d2cec 100644 --- a/Sources/LiveObject.swift +++ b/Sources/LiveObject.swift @@ -50,8 +50,6 @@ public final class LiveObject: ObjectRepresentation, Hashable return self.lazySnapshot } - - public private(set) lazy var object: O = self.context.fetchExisting(self.objectID)! public func addObserver(_ observer: T, _ callback: @escaping (LiveObject) -> Void) { @@ -76,27 +74,49 @@ public final class LiveObject: ObjectRepresentation, Hashable public typealias ObjectType = O - public static func cs_fromRaw(object: NSManagedObject) -> Self { + public func objectID() -> O.ObjectID { - return self.init( - objectID: object.objectID, - context: object.managedObjectContext! - ) + return self.id } - public func cs_id() -> O.ObjectID { + public func asLiveObject(in dataStack: DataStack) -> LiveObject? { - return self.objectID + let context = dataStack.unsafeContext() + if self.context == context { + + return self + } + return Self.init(objectID: self.id, context: context) } - public func cs_object() -> O? { + public func asEditable(in transaction: BaseDataTransaction) -> O? { - return self.context.fetchExisting(self.objectID) + return self.context.fetchExisting(self.id) } - public func cs_rawObject(in context: NSManagedObjectContext) -> NSManagedObject? { + public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot? { - return self.object.cs_toRaw() + let context = dataStack.unsafeContext() + if self.context == context { + + return self.lazySnapshot + } + return .init(id: self.id, context: context) + } + + public func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot? { + + let context = transaction.unsafeContext() + if self.context == context { + + return self.lazySnapshot + } + return .init(id: self.id, context: context) + } + + public func asObjectMonitor(in dataStack: DataStack) -> ObjectMonitor? { + + return .init(objectID: self.id, context: dataStack.unsafeContext()) } @@ -104,7 +124,7 @@ public final class LiveObject: ObjectRepresentation, Hashable public static func == (_ lhs: LiveObject, _ rhs: LiveObject) -> Bool { - return lhs.objectID == rhs.objectID + return lhs.id == rhs.id && lhs.context == rhs.context } @@ -113,7 +133,7 @@ public final class LiveObject: ObjectRepresentation, Hashable public func hash(into hasher: inout Hasher) { - hasher.combine(self.objectID) + hasher.combine(self.id) hasher.combine(self.context) } @@ -141,7 +161,7 @@ public final class LiveObject: ObjectRepresentation, Hashable fileprivate init(objectID: O.ObjectID, context: NSManagedObjectContext, initializer: @escaping (NSManagedObjectID, NSManagedObjectContext) -> ObjectSnapshot) { - self.objectID = objectID + self.id = objectID self.context = context if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) { @@ -175,7 +195,7 @@ public final class LiveObject: ObjectRepresentation, Hashable // MARK: Private - private let objectID: O.ObjectID + private let id: O.ObjectID private let context: NSManagedObjectContext @Internals.LazyNonmutating(uninitialized: ()) diff --git a/Sources/ObjectMonitor.swift b/Sources/ObjectMonitor.swift index 584b8e9..0641123 100644 --- a/Sources/ObjectMonitor.swift +++ b/Sources/ObjectMonitor.swift @@ -122,6 +122,44 @@ public final class ObjectMonitor: ObjectRepresentation, Equata // MARK: ObjectRepresentation + public func objectID() -> D.ObjectID { + + return self.id + } + + public func asLiveObject(in dataStack: DataStack) -> LiveObject? { + + let context = dataStack.unsafeContext() + return .init(objectID: self.id, context: context) + } + + public func asEditable(in transaction: BaseDataTransaction) -> D? { + + return self.context.fetchExisting(self.id) + } + + public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot? { + + let context = dataStack.unsafeContext() + return .init(id: self.id, context: context) + } + + public func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot? { + + let context = transaction.unsafeContext() + return .init(id: self.id, context: context) + } + + public func asObjectMonitor(in dataStack: DataStack) -> ObjectMonitor? { + + let context = dataStack.unsafeContext() + if self.context == context { + + return self + } + return .init(objectID: self.id, context: dataStack.unsafeContext()) + } + public typealias ObjectType = D public static func cs_fromRaw(object: NSManagedObject) -> Self { @@ -181,14 +219,34 @@ public final class ObjectMonitor: ObjectRepresentation, Equata // MARK: Internal - internal convenience init(dataStack: DataStack, object: O) where O.ObjectType == ObjectType { + internal init(objectID: ObjectType.ObjectID, context: NSManagedObjectContext) { - self.init(context: dataStack.mainContext, objectID: object.cs_id()) - } - - internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: O) where O.ObjectType == ObjectType { + let fetchRequest = Internals.CoreStoreFetchRequest() + fetchRequest.entity = objectID.entity + fetchRequest.fetchLimit = 0 + fetchRequest.resultType = .managedObjectResultType + fetchRequest.sortDescriptors = [] + fetchRequest.includesPendingChanges = false + fetchRequest.shouldRefreshRefetchedObjects = true - self.init(context: unsafeTransaction.context, objectID: object.cs_id()) + let fetchedResultsController = Internals.CoreStoreFetchedResultsController( + context: context, + fetchRequest: fetchRequest, + from: From([objectID.persistentStore?.configurationName]), + applyFetchClauses: Where("SELF", isEqualTo: objectID).applyToFetchRequest + ) + + let fetchedResultsControllerDelegate = Internals.FetchedResultsControllerDelegate() + + self.objectID = objectID + self.fetchedResultsController = fetchedResultsController + self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate + + fetchedResultsControllerDelegate.handler = self + fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController + try! fetchedResultsController.performFetchFromSpecifiedStores() + + self.lastCommittedAttributes = (self.object?.cs_toRaw().committedValues(forKeys: nil) as? [String: NSObject]) ?? [:] } 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) { @@ -282,36 +340,6 @@ public final class ObjectMonitor: ObjectRepresentation, Equata private var didDeleteObjectKey: Void? private var didUpdateObjectKey: Void? - private init(context: NSManagedObjectContext, objectID: ObjectType.ObjectID) { - - let fetchRequest = Internals.CoreStoreFetchRequest() - fetchRequest.entity = objectID.entity - fetchRequest.fetchLimit = 0 - fetchRequest.resultType = .managedObjectResultType - fetchRequest.sortDescriptors = [] - fetchRequest.includesPendingChanges = false - fetchRequest.shouldRefreshRefetchedObjects = true - - let fetchedResultsController = Internals.CoreStoreFetchedResultsController( - context: context, - fetchRequest: fetchRequest, - from: From([objectID.persistentStore?.configurationName]), - applyFetchClauses: Where("SELF", isEqualTo: objectID).applyToFetchRequest - ) - - let fetchedResultsControllerDelegate = Internals.FetchedResultsControllerDelegate() - - self.objectID = objectID - self.fetchedResultsController = fetchedResultsController - self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate - - fetchedResultsControllerDelegate.handler = self - fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController - try! fetchedResultsController.performFetchFromSpecifiedStores() - - 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) { Internals.setAssociatedRetainedObject( diff --git a/Sources/ObjectRepresentation.swift b/Sources/ObjectRepresentation.swift index 6fd7eb5..0f118d4 100644 --- a/Sources/ObjectRepresentation.swift +++ b/Sources/ObjectRepresentation.swift @@ -39,35 +39,66 @@ public protocol ObjectRepresentation { associatedtype ObjectType: DynamicObject /** - Used internally by CoreStore. Do not call directly. + The internal ID for the object. */ - static func cs_fromRaw(object: NSManagedObject) -> Self + func objectID() -> ObjectType.ObjectID /** - Used internally by CoreStore. Do not call directly. + An instance that may be observed for object changes. */ - func cs_id() -> ObjectType.ObjectID + func asLiveObject(in dataStack: DataStack) -> LiveObject? /** - Used internally by CoreStore. Do not call directly. + An instance that may be mutated within a transaction. */ - func cs_object() -> ObjectType? + func asEditable(in transaction: BaseDataTransaction) -> ObjectType? /** - Used internally by CoreStore. Do not call directly. + A thread-safe `struct` that is a full-copy of the object's properties */ - func cs_rawObject(in context: NSManagedObjectContext) -> NSManagedObject? + func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot? + + /** + A thread-safe `struct` that is a full-copy of the object's properties + */ + func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot? + + /** + An instance that may be observed for property-specific changes. + */ + func asObjectMonitor(in dataStack: DataStack) -> ObjectMonitor? } -extension NSManagedObject: ObjectRepresentation {} +extension LiveObject { + + public static func cs_object(in context: NSManagedObjectContext) -> LiveObject? { + + return nil + } +} -extension CoreStoreObject: ObjectRepresentation {} +extension ObjectMonitor { + + public static func cs_object(in context: NSManagedObjectContext) -> LiveObject? { + + return nil + } +} + +extension NSManagedObject: ObjectRepresentation { + + +} + +extension CoreStoreObject: ObjectRepresentation { + +} extension DynamicObject where Self: ObjectRepresentation { - public func cs_object() -> Self? { + public static func cs_object(in context: NSManagedObjectContext) -> LiveObject? { - return self + fatalError() } public func cs_rawObject(in context: NSManagedObjectContext) -> NSManagedObject? { diff --git a/Sources/ObjectSnapshot.swift b/Sources/ObjectSnapshot.swift index 11d7a66..765def5 100644 --- a/Sources/ObjectSnapshot.swift +++ b/Sources/ObjectSnapshot.swift @@ -37,16 +37,55 @@ import AppKit // MARK: - ObjectSnapshot @dynamicMemberLookup -public struct ObjectSnapshot: SnapshotResult, Identifiable, Hashable { +public struct ObjectSnapshot: SnapshotResult, ObjectRepresentation, Hashable { // MARK: SnapshotResult public typealias ObjectType = O - - - // MARK: Identifiable - - public let id: O.ObjectID + + + // MARK: ObjectRepresentation + + public func objectID() -> O.ObjectID { + + return self.id + } + + public func asLiveObject(in dataStack: DataStack) -> LiveObject? { + + let context = dataStack.unsafeContext() + return .init(objectID: self.id, context: context) + } + + public func asEditable(in transaction: BaseDataTransaction) -> O? { + + return self.context.fetchExisting(self.id) + } + + public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot? { + + let context = dataStack.unsafeContext() + if self.context == context { + + return self + } + return .init(id: self.id, context: context) + } + + public func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot? { + + let context = transaction.unsafeContext() + if self.context == context { + + return self + } + return .init(id: self.id, context: context) + } + + public func asObjectMonitor(in dataStack: DataStack) -> ObjectMonitor? { + + return .init(objectID: self.id, context: dataStack.unsafeContext()) + } // MARK: Equatable @@ -79,6 +118,7 @@ public struct ObjectSnapshot: SnapshotResult, Identifiable, Ha // MARK: Private + private let id: O.ObjectID private let context: NSManagedObjectContext private let values: NSDictionary }