diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift index b3ee95f..6ab1fa2 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift @@ -16,13 +16,13 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { func setPalette(_ newValue: O?) where O.ObjectType == Palette { - guard self.monitor?.objectID() != newValue?.objectID() else { + guard self.monitor?.object?.objectID() != newValue?.objectID() else { return } if let newValue = newValue { - self.monitor = newValue.asObjectMonitor(in: ColorsDemo.stack) + self.monitor = newValue.asReadOnly(in: ColorsDemo.stack).map(ColorsDemo.stack.monitorObject(_:)) } else { diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift index 73f44e0..98ddd76 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift @@ -73,9 +73,30 @@ extension Palette { return "H: \(self.hue.value)˚, S: \(round(self.saturation.value * 100.0))%, B: \(round(self.brightness.value * 100.0))%" } - +} + +extension LiveObject where ObjectType == Palette { + + var color: UIColor { + + return UIColor( + hue: CGFloat(self.hue) / 360.0, + saturation: CGFloat(self.saturation), + brightness: CGFloat(self.brightness), + alpha: 1.0 + ) + } + + var colorText: String { + + return "H: \(self.hue)˚, S: \(round(self.saturation * 100.0))%, B: \(round(self.brightness * 100.0))%" + } +} + +extension Palette { + func setInitialValues(in transaction: BaseDataTransaction) { - + self.hue .= Palette.randomHue() self.saturation .= Float(1.0) self.brightness .= Float(arc4random_uniform(70) + 30) / 100.0 diff --git a/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift b/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift index e18bc3b..7b52c71 100644 --- a/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift +++ b/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift @@ -20,22 +20,6 @@ struct SwiftUIView: View { @ObservedObject var palettes: LiveList - @available(iOS 13.0.0, *) - struct ColorCell: View { - - @ObservedObject - var palette: LiveObject - - var body: some View { - HStack { - Color(palette.color) - .cornerRadius(5) - .frame(width: 30, height: 30, alignment: .leading) - Text(palette.colorText) - } - } - } - var body: some View { NavigationView { List { @@ -124,6 +108,22 @@ struct SwiftUIView: View { private var needsShowAlert = false } +@available(iOS 13.0.0, *) +struct ColorCell: View { + + @ObservedObject + var palette: LiveObject + + var body: some View { + HStack { + Color(palette.color) + .cornerRadius(5) + .frame(width: 30, height: 30, alignment: .leading) + Text(palette.colorText) + } + } +} + @available(iOS 13.0.0, *) struct DetailView: View { diff --git a/Sources/DiffableDataSource.CollectionView.swift b/Sources/DiffableDataSource.CollectionView.swift index 3d89081..8b4fa93 100644 --- a/Sources/DiffableDataSource.CollectionView.swift +++ b/Sources/DiffableDataSource.CollectionView.swift @@ -42,7 +42,7 @@ extension DiffableDataSource { public typealias ObjectType = O @nonobjc - public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, ObjectType) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) { + public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) { self.collectionView = collectionView self.cellProvider = cellProvider @@ -77,7 +77,7 @@ extension DiffableDataSource { collectionView.dataSource = self } - public func apply(_ snapshot: ListSnapshot, animatingDifferences: Bool = true) { + public func apply(_ snapshot: ListSnapshot, animatingDifferences: Bool = true) { let diffableSnapshot = snapshot.diffableSnapshot // if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) { @@ -199,7 +199,7 @@ extension DiffableDataSource { private weak var collectionView: UICollectionView? private let dataStack: DataStack - private let cellProvider: (UICollectionView, IndexPath, ObjectType) -> UICollectionViewCell? + private let cellProvider: (UICollectionView, IndexPath, O) -> UICollectionViewCell? private let supplementaryViewProvider: (UICollectionView, String, IndexPath) -> UICollectionReusableView? private var rawDataSource: Any! diff --git a/Sources/DiffableDataSource.TableView.swift b/Sources/DiffableDataSource.TableView.swift index 19060f2..69976e8 100644 --- a/Sources/DiffableDataSource.TableView.swift +++ b/Sources/DiffableDataSource.TableView.swift @@ -48,7 +48,7 @@ extension DiffableDataSource { public typealias ObjectType = O @nonobjc - public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, ObjectType) -> UITableViewCell?) { + public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) { self.tableView = tableView self.cellProvider = cellProvider @@ -82,7 +82,7 @@ extension DiffableDataSource { tableView.dataSource = self } - public func apply(_ snapshot: ListSnapshot, animatingDifferences: Bool = true) { + public func apply(_ snapshot: ListSnapshot, animatingDifferences: Bool = true) { let diffableSnapshot = snapshot.diffableSnapshot // if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) { @@ -221,7 +221,7 @@ extension DiffableDataSource { private weak var tableView: UITableView? private let dataStack: DataStack - private let cellProvider: (UITableView, IndexPath, ObjectType) -> UITableViewCell? + private let cellProvider: (UITableView, IndexPath, O) -> UITableViewCell? private var rawDataSource: Any! // @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) diff --git a/Sources/LiveObject.swift b/Sources/LiveObject.swift index 3ed1c10..99034ae 100644 --- a/Sources/LiveObject.swift +++ b/Sources/LiveObject.swift @@ -88,10 +88,15 @@ public final class LiveObject: ObjectRepresentation, Hashable } return Self.init(objectID: self.id, context: context) } + + public func asReadOnly(in dataStack: DataStack) -> O? { + + return dataStack.unsafeContext().fetchExisting(self.id) + } public func asEditable(in transaction: BaseDataTransaction) -> O? { - return self.context.fetchExisting(self.id) + return transaction.unsafeContext().fetchExisting(self.id) } public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot? { @@ -113,11 +118,6 @@ public final class LiveObject: ObjectRepresentation, Hashable } return .init(objectID: self.id, context: context) } - - public func asObjectMonitor(in dataStack: DataStack) -> ObjectMonitor? { - - return .init(objectID: self.id, context: dataStack.unsafeContext()) - } // MARK: Equatable @@ -354,12 +354,4 @@ extension LiveObject where O: CoreStoreObject { return self.snapshot[dynamicMember: member] } - - /** - Returns the value for the property identified by a given key. - */ - public subscript(dynamicMember member: KeyPath) -> T { - - return self.object[keyPath: member] - } } diff --git a/Sources/ObjectMonitor.swift b/Sources/ObjectMonitor.swift index 8dc109f..9a9fa3a 100644 --- a/Sources/ObjectMonitor.swift +++ b/Sources/ObjectMonitor.swift @@ -40,17 +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(macOS 10.12, *) -public final class ObjectMonitor: ObjectRepresentation, Equatable { +public final class ObjectMonitor: Equatable { + + /** + The object type represented by this `ObjectMonitor` + */ + public typealias ObjectType = O /** Returns the `DynamicObject` instance being observed, or `nil` if the object was already deleted. */ - public var object: ObjectType? { + public var object: O? { return self.fetchedResultsController .fetchedObjects? .first - .flatMap({ ObjectType.cs_fromRaw(object: $0) }) + .flatMap({ O.cs_fromRaw(object: $0) }) } /** @@ -72,7 +77,7 @@ public final class ObjectMonitor: ObjectRepresentation, Equata - parameter observer: an `ObjectObserver` to send change notifications to */ - public func addObserver(_ observer: U) where U.ObjectEntityType == ObjectType { + public func addObserver(_ observer: U) where U.ObjectEntityType == O { self.unregisterObserver(observer) self.registerObserver( @@ -99,7 +104,7 @@ public final class ObjectMonitor: ObjectRepresentation, Equata - parameter observer: an `ObjectObserver` to unregister notifications to */ - public func removeObserver(_ observer: U) where U.ObjectEntityType == ObjectType { + public func removeObserver(_ observer: U) where U.ObjectEntityType == O { self.unregisterObserver(observer) } @@ -120,50 +125,9 @@ public final class ObjectMonitor: ObjectRepresentation, Equata public let userInfo = UserInfo() - // 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() - return .init(objectID: self.id, context: context) - } - - public func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot? { - - let context = transaction.unsafeContext() - return .init(objectID: 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()) - } - - // MARK: Equatable - public static func == (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { + public static func == (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { return lhs === rhs } @@ -173,7 +137,7 @@ public final class ObjectMonitor: ObjectRepresentation, Equata return lhs.fetchedResultsController === rhs.fetchedResultsController } - public static func ~= (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { + public static func ~= (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { return lhs === rhs } @@ -194,7 +158,7 @@ public final class ObjectMonitor: ObjectRepresentation, Equata // MARK: Internal - internal init(objectID: ObjectType.ObjectID, context: NSManagedObjectContext) { + internal init(objectID: O.ObjectID, context: NSManagedObjectContext) { let fetchRequest = Internals.CoreStoreFetchRequest() fetchRequest.entity = objectID.entity @@ -207,8 +171,8 @@ public final class ObjectMonitor: ObjectRepresentation, Equata let fetchedResultsController = Internals.CoreStoreFetchedResultsController( context: context, fetchRequest: fetchRequest, - from: From([objectID.persistentStore?.configurationName]), - applyFetchClauses: Where("SELF", isEqualTo: objectID).applyToFetchRequest + from: From([objectID.persistentStore?.configurationName]), + applyFetchClauses: Where("SELF", isEqualTo: objectID).applyToFetchRequest ) let fetchedResultsControllerDelegate = Internals.FetchedResultsControllerDelegate() @@ -224,7 +188,7 @@ public final class ObjectMonitor: ObjectRepresentation, Equata 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) { + internal func registerObserver(_ observer: U, willChangeObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: O) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: O) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: O, _ changedPersistentKeys: Set) -> Void) { Internals.assert( Thread.isMainThread, @@ -320,7 +284,7 @@ public final class ObjectMonitor: ObjectRepresentation, Equata return self.fetchedResultsController.managedObjectContext } - 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) { Internals.setAssociatedRetainedObject( Internals.NotificationObserver( @@ -340,7 +304,7 @@ public final class ObjectMonitor: ObjectRepresentation, Equata ) } - private func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor, _ object: ObjectType) -> Void) { + private func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor, _ object: O) -> Void) { Internals.setAssociatedRetainedObject( Internals.NotificationObserver( @@ -354,7 +318,7 @@ public final class ObjectMonitor: ObjectRepresentation, Equata return } - callback(self, ObjectType.cs_fromRaw(object: object)) + callback(self, O.cs_fromRaw(object: object)) } ), forKey: notificationKey, diff --git a/Sources/ObjectRepresentation.swift b/Sources/ObjectRepresentation.swift index f33899a..85fba5d 100644 --- a/Sources/ObjectRepresentation.swift +++ b/Sources/ObjectRepresentation.swift @@ -47,9 +47,14 @@ public protocol ObjectRepresentation { An instance that may be observed for object changes. */ func asLiveObject(in dataStack: DataStack) -> LiveObject? + + /** + A read-only instance in the `DataStack`. + */ + func asReadOnly(in dataStack: DataStack) -> ObjectType? /** - An instance that may be mutated within a transaction. + An instance that may be mutated within a `BaseDataTransaction`. */ func asEditable(in transaction: BaseDataTransaction) -> ObjectType? @@ -62,11 +67,6 @@ public protocol ObjectRepresentation { 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 {} @@ -88,6 +88,16 @@ extension DynamicObject where Self: ObjectRepresentation { return .init(objectID: self.cs_id(), context: context) } + public func asReadOnly(in dataStack: DataStack) -> Self? { + + let context = dataStack.unsafeContext() + if self.cs_toRaw().managedObjectContext == context { + + return self + } + return context.fetchExisting(self.cs_id()) + } + public func asEditable(in transaction: BaseDataTransaction) -> Self? { let context = transaction.unsafeContext() @@ -109,10 +119,4 @@ extension DynamicObject where Self: ObjectRepresentation { let context = transaction.unsafeContext() return .init(objectID: self.cs_id(), context: context) } - - public func asObjectMonitor(in dataStack: DataStack) -> ObjectMonitor? { - - let context = dataStack.unsafeContext() - return .init(objectID: self.cs_id(), context: context) - } } diff --git a/Sources/ObjectSnapshot.swift b/Sources/ObjectSnapshot.swift index 279bb77..a6be1be 100644 --- a/Sources/ObjectSnapshot.swift +++ b/Sources/ObjectSnapshot.swift @@ -56,10 +56,15 @@ public struct ObjectSnapshot: SnapshotResult, ObjectRepresenta let context = dataStack.unsafeContext() return .init(objectID: self.id, context: context) } + + public func asReadOnly(in dataStack: DataStack) -> O? { + + return dataStack.unsafeContext().fetchExisting(self.id) + } public func asEditable(in transaction: BaseDataTransaction) -> O? { - return self.context.fetchExisting(self.id) + return transaction.unsafeContext().fetchExisting(self.id) } public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot? { @@ -81,11 +86,6 @@ public struct ObjectSnapshot: SnapshotResult, ObjectRepresenta } return .init(objectID: self.id, context: context) } - - public func asObjectMonitor(in dataStack: DataStack) -> ObjectMonitor? { - - return .init(objectID: self.id, context: dataStack.unsafeContext()) - } // MARK: Equatable @@ -112,7 +112,7 @@ public struct ObjectSnapshot: SnapshotResult, ObjectRepresenta self.id = objectID self.context = context - self.values = O.cs_snapshotDictionary(id: id, context: context) as NSDictionary + self.values = O.cs_snapshotDictionary(id: objectID, context: context) as NSDictionary }