WIP: ObjectRepresentable utilities

This commit is contained in:
John Estropia
2019-10-14 21:36:03 +09:00
parent f5a165d47d
commit 6b64eb7650
17 changed files with 315 additions and 198 deletions

View File

@@ -136,14 +136,15 @@ class ListObserverDemoViewController: UITableViewController {
return cell
}
)
ColorsDemo.palettes.addObserver(self) { [weak self] (liveList, snapshot) in
ColorsDemo.palettes.addObserver(self) { [weak self] (liveList) in
guard let self = self else {
return
}
self.dataSource?.apply(snapshot, animatingDifferences: true)
self.dataSource?.apply(liveList.snapshot, animatingDifferences: true)
}
self.dataSource?.apply(ColorsDemo.palettes.snapshot, animatingDifferences: false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
@@ -152,8 +153,8 @@ class ListObserverDemoViewController: UITableViewController {
switch (segue.identifier, segue.destination, sender) {
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as Palette):
destinationViewController.palette = palette
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as LiveObject<Palette>):
destinationViewController.setPalette(palette)
default:
break
@@ -169,7 +170,7 @@ class ListObserverDemoViewController: UITableViewController {
self.performSegue(
withIdentifier: "ObjectObserverDemoViewController",
sender: ColorsDemo.palettes[indexPath: indexPath]?.object
sender: ColorsDemo.palettes[indexPath: indexPath]
)
}

View File

@@ -14,30 +14,23 @@ import CoreStore
class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
var palette: Palette? {
func setPalette<O: ObjectRepresentation>(_ newValue: O?) where O.ObjectType == Palette {
get {
guard self.monitor?.cs_id() != newValue?.cs_id() else {
return self.monitor?.object
return
}
set {
if let newValue = newValue {
guard self.monitor?.object != newValue else {
return
}
self.monitor = ColorsDemo.stack.monitorObject(newValue)
}
else {
if let palette = newValue {
self.monitor = ColorsDemo.stack.monitorObject(palette)
}
else {
self.monitor = nil
}
self.monitor = nil
}
}
// MARK: NSObject
deinit {

View File

@@ -108,51 +108,35 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
return super.edit(into, objectID)
}
/**
Deletes a specified `NSManagedObject` or `CoreStoreObject`.
- parameter object: the `NSManagedObject` or `CoreStoreObject` to be deleted
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by series of `ObjectRepresentation`s.
- parameter object: the `ObjectRepresentation` representing an `NSManagedObject` or `CoreStoreObject` to be deleted
- parameter objects: other `ObjectRepresentation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public override func delete<D: DynamicObject>(_ object: D?) {
Internals.assert(
!self.isCommitted,
"Attempted to delete an entity of type \(Internals.typeName(object)) from an already committed \(Internals.typeName(self))."
)
super.delete(object)
}
/**
Deletes the specified `DynamicObject`s.
- parameter object1: the `DynamicObject` to be deleted
- parameter object2: another `DynamicObject` to be deleted
- parameter objects: other `DynamicObject`s to be deleted
*/
public override func delete<D: DynamicObject>(_ object1: D?, _ object2: D?, _ objects: D?...) {
public override func delete<O: ObjectRepresentation>(_ object: O?, _ objects: O?...) {
Internals.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
)
super.delete(([object1, object2] + objects).compactMap { $0 })
super.delete(([object] + objects).compactMap { $0 })
}
/**
Deletes the specified `DynamicObject`s.
- parameter objects: the `DynamicObject`s to be deleted
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by an `ObjectRepresenation`.
- parameter objects: the `ObjectRepresenation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public override func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: DynamicObject {
public override func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: ObjectRepresentation {
Internals.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
)
super.delete(objects)
}

View File

@@ -156,47 +156,36 @@ public /*abstract*/ class BaseDataTransaction {
}
/**
Deletes a specified `NSManagedObject` or `CoreStoreObject`.
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by series of `ObjectRepresentation`s.
- parameter object: the `NSManagedObject` or `CoreStoreObject` to be deleted
- parameter object: the `ObjectRepresentation` representing an `NSManagedObject` or `CoreStoreObject` to be deleted
- parameter objects: other `ObjectRepresentation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public func delete<D: DynamicObject>(_ object: D?) {
public func delete<O: ObjectRepresentation>(_ object: O?, _ objects: O?...) {
Internals.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete an entity outside its designated queue."
)
let context = self.context
object
.flatMap(context.fetchExisting)
.flatMap({ context.delete($0.cs_toRaw()) })
self.delete(([object] + objects).compactMap { $0 })
}
/**
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s.
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by an `ObjectRepresenation`.
- parameter object1: the `NSManagedObject` or `CoreStoreObject` to be deleted
- parameter object2: another `NSManagedObject` or `CoreStoreObject` to be deleted
- parameter objects: other `NSManagedObject`s or `CoreStoreObject`s to be deleted
- parameter objects: the `ObjectRepresenation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public func delete<D: DynamicObject>(_ object1: D?, _ object2: D?, _ objects: D?...) {
self.delete(([object1, object2] + objects).compactMap { $0 })
}
/**
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s.
- parameter objects: the `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: DynamicObject {
public func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: ObjectRepresentation {
Internals.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete entities outside their designated queue."
)
let context = self.context
objects.forEach { context.fetchExisting($0).flatMap({ context.delete($0.cs_toRaw()) }) }
objects.forEach {
$0.cs_rawObject(in: context).map({ context.delete($0) })
}
}
/**
@@ -217,10 +206,10 @@ public /*abstract*/ class BaseDataTransaction {
/**
Returns `true` if the object has any property values changed. This method should not be called after the `commit()` method was called.
- parameter entity: the `DynamicObject` instance
- parameter object: the `DynamicObject` instance
- returns: `true` if the object has any property values changed.
*/
public func objectHasPersistentChangedValues<D: DynamicObject>(_ entity: D) -> Bool {
public func objectHasPersistentChangedValues<D: DynamicObject>(_ object: D) -> Bool {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -230,7 +219,7 @@ public /*abstract*/ class BaseDataTransaction {
!self.isCommitted,
"Attempted to access inserted objects from an already committed \(Internals.typeName(self))."
)
return entity.cs_toRaw().hasPersistentChangedValues
return object.cs_toRaw().hasPersistentChangedValues
}
/**

View File

@@ -34,12 +34,12 @@ import CoreData
extension CoreStore {
/**
Using the `defaultStack`, creates an `ObjectMonitor` for the specified `DynamicObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `DynamicObject`.
Using the `defaultStack`, creates an `ObjectMonitor` for the specified `ObjectRepresentation`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `DynamicObject`.
- parameter object: the `DynamicObject` to observe changes from
- parameter object: the `ObjectRepresentation` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
public static func monitorObject<D>(_ object: D) -> ObjectMonitor<D> {
public static func monitorObject<O: ObjectRepresentation>(_ object: O) -> ObjectMonitor<O.ObjectType> {
return Shared.defaultStack.monitorObject(object)
}

View File

@@ -33,12 +33,12 @@ import CoreData
extension DataStack {
/**
Creates an `ObjectMonitor` for the specified `DynamicObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `DynamicObject`.
Creates an `ObjectMonitor` for the specified `ObjectRepresentation`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `DynamicObject`.
- parameter object: the `DynamicObject` to observe changes from
- parameter object: the `ObjectRepresentation` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
public func monitorObject<D>(_ object: D) -> ObjectMonitor<D> {
public func monitorObject<O: ObjectRepresentation>(_ object: O) -> ObjectMonitor<O.ObjectType> {
Internals.assert(
Thread.isMainThread,

View File

@@ -33,22 +33,21 @@ import CoreData
All CoreStore's utilities are designed around `DynamicObject` instances. `NSManagedObject` and `CoreStoreObject` instances all conform to `DynamicObject`.
*/
public protocol DynamicObject: AnyObject {
/**
The object ID for this instance
*/
typealias ObjectID = NSManagedObjectID
/**
Used internally by CoreStore. Do not call directly.
*/
static func cs_forceCreate(entityDescription: NSEntityDescription, into context: NSManagedObjectContext, assignTo store: NSPersistentStore) -> Self
/**
Used internally by CoreStore. Do not call directly.
*/
static func cs_snapshotDictionary(id: ObjectID, context: NSManagedObjectContext) -> [String: Any]
/**
Used internally by CoreStore. Do not call directly.
*/
@@ -62,22 +61,17 @@ public protocol DynamicObject: AnyObject {
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_id() -> ObjectID
func cs_toRaw() -> NSManagedObject
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_toRaw() -> NSManagedObject
func cs_id() -> ObjectID
}
extension DynamicObject {
// MARK: Internal
// internal static func keyPathBuilder() -> DynamicObjectMeta<Never, Self> {
//
// return .init(keyPathString: "SELF")
// }
// MARK: Internal
internal func runtimeType() -> Self.Type {
@@ -120,15 +114,15 @@ extension NSManagedObject: DynamicObject {
return object.isKind(of: self)
}
public func cs_id() -> ObjectID {
return self.objectID
}
public func cs_toRaw() -> NSManagedObject {
return self
}
public func cs_id() -> ObjectID {
return self.objectID
}
}
@@ -210,13 +204,13 @@ extension CoreStoreObject {
return (self as AnyClass).isSubclass(of: type as AnyClass)
}
public func cs_id() -> ObjectID {
return self.rawObject!.objectID
}
public func cs_toRaw() -> NSManagedObject {
return self.rawObject!
}
public func cs_id() -> ObjectID {
return self.rawObject!.objectID
}
}

View File

@@ -116,12 +116,16 @@ extension Internals {
@objc
dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
var snapshot = Internals.DiffableDataSourceSnapshot(
sections: controller.sections ?? []
)
snapshot.reloadItems(self.reloadedIDs)
self.handler?.controller(
controller,
didChangeContentWith: Internals.DiffableDataSourceSnapshot(
sections: controller.sections ?? []
)
didChangeContentWith: snapshot
)
self.reloadedIDs.removeAll()
}
@objc
@@ -132,5 +136,17 @@ extension Internals {
sectionIndexTitleForSectionName: sectionName
)
}
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
let object = anObject as! NSManagedObject
self.reloadedIDs.append(object.objectID)
}
// MARK: Private
private var reloadedIDs: [NSManagedObjectID] = []
}
}

View File

@@ -63,6 +63,11 @@ extension Internals {
self.observers.setObject(Closure<T, Void>(closure), forKey: observer)
}
internal func removeObserver<U: AnyObject>(_ observer: U) {
self.observers.removeObject(forKey: observer)
}
// MARK: Private

View File

@@ -68,7 +68,7 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
return indices.map { position in
let itemID = itemIDs[position]
return LiveObject<O>(id: itemID, context: context)
return LiveObject<O>(objectID: itemID, context: context)
}
}
@@ -78,7 +78,7 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
return itemIDs.map {
return LiveObject<O>(id: $0, context: context)
return LiveObject<O>(objectID: $0, context: context)
}
}
@@ -89,7 +89,7 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
return itemIndices.map { position in
let itemID = itemIDs[position]
return LiveObject<O>(id: itemID, context: context)
return LiveObject<O>(objectID: itemID, context: context)
}
}
@@ -241,7 +241,7 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
let context = self.context!
let itemID = self.diffableSnapshot.itemIdentifiers[position]
return LiveObject<O>(id: itemID, context: context)
return LiveObject<O>(objectID: itemID, context: context)
}

View File

@@ -50,7 +50,7 @@ public final class LiveList<O: DynamicObject>: Hashable {
let context = self.context
return self.snapshot
.itemIdentifiers(inSection: sectionID)
.map({ context.liveObject(id: $0) })
.map({ context.liveObject(objectID: $0) })
}
public subscript(itemID itemID: ItemID) -> LiveObject<O>? {
@@ -59,7 +59,7 @@ public final class LiveList<O: DynamicObject>: Hashable {
return nil
}
return self.context.liveObject(id: validID)
return self.context.liveObject(objectID: validID)
}
public subscript(indexPath indexPath: IndexPath) -> LiveObject<O>? {
@@ -77,7 +77,7 @@ public final class LiveList<O: DynamicObject>: Hashable {
return nil
}
let itemID = itemIdentifiers[indexPath.item]
return self.context.liveObject(id: itemID)
return self.context.liveObject(objectID: itemID)
}
public subscript<S: Sequence>(section sectionID: SectionID, itemIndices itemIndices: S) -> [LiveObject<O>] where S.Element == Int {
@@ -87,7 +87,7 @@ public final class LiveList<O: DynamicObject>: Hashable {
return itemIndices.map { position in
let itemID = itemIDs[position]
return context.liveObject(id: itemID)
return context.liveObject(objectID: itemID)
}
}
@@ -99,7 +99,7 @@ public final class LiveList<O: DynamicObject>: Hashable {
}
didSet {
self.notifyObservers(self.snapshot)
self.notifyObservers()
self.didChange()
}
}
@@ -123,7 +123,7 @@ public final class LiveList<O: DynamicObject>: Hashable {
let context = self.context
return self.snapshot.itemIdentifiers
.map({ context.liveObject(id: $0) })
.map({ context.liveObject(objectID: $0) })
}
public func numberOfItems(inSection identifier: SectionID) -> Int {
@@ -136,7 +136,7 @@ public final class LiveList<O: DynamicObject>: Hashable {
let context = self.context
return self.snapshot
.itemIdentifiers(inSection: identifier)
.map({ context.liveObject(id: $0) })
.map({ context.liveObject(objectID: $0) })
}
public func items(inSection identifier: SectionID, atIndices indices: IndexSet) -> [LiveObject<O>] {
@@ -146,18 +146,18 @@ public final class LiveList<O: DynamicObject>: Hashable {
return indices.map { position in
let itemID = itemIDs[position]
return context.liveObject(id: itemID)
return context.liveObject(objectID: itemID)
}
}
public func section(containingItem item: LiveObject<O>) -> SectionID? {
return self.snapshot.sectionIdentifier(containingItem: item.id)
return self.snapshot.sectionIdentifier(containingItem: item.cs_id())
}
public func indexOfItem(_ item: LiveObject<O>) -> Int? {
return self.snapshot.indexOfItem(item.id)
return self.snapshot.indexOfItem(item.cs_id())
}
public func indexOfSection(_ identifier: SectionID) -> Int? {
@@ -165,14 +165,12 @@ public final class LiveList<O: DynamicObject>: Hashable {
return self.snapshot.indexOfSection(identifier)
}
public func addObserver<T: AnyObject>(_ observer: T, _ callback: @escaping (LiveList<O>, ListSnapshot<O>) -> Void) {
public func addObserver<T: AnyObject>(_ observer: T, _ callback: @escaping (LiveList<O>) -> Void) {
self.observers.setObject(
Internals.Closure(callback),
forKey: observer
)
callback(self, self.snapshot)
}
public func removeObserver<T: AnyObject>(_ observer: T) {
@@ -272,7 +270,7 @@ public final class LiveList<O: DynamicObject>: Hashable {
private let from: From<ObjectType>
private let sectionBy: SectionBy<ObjectType>?
private let observers: NSMapTable<AnyObject, Internals.Closure<(LiveList<O>, ListSnapshot<O>), Void>> = .weakToStrongObjects()
private lazy var observers: NSMapTable<AnyObject, Internals.Closure<LiveList<O>, Void>> = .weakToStrongObjects()
private lazy var context: NSManagedObjectContext = self.fetchedResultsController.managedObjectContext
@@ -338,7 +336,7 @@ public final class LiveList<O: DynamicObject>: Hashable {
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
}
private func notifyObservers(_ snapshot: ListSnapshot<O>) {
private func notifyObservers() {
guard let enumerator = self.observers.objectEnumerator() else {
@@ -346,7 +344,7 @@ public final class LiveList<O: DynamicObject>: Hashable {
}
for closure in enumerator {
(closure as! Internals.Closure<(LiveList<O>, ListSnapshot<O>), Void>).invoke(with: (self, snapshot))
(closure as! Internals.Closure<LiveList<O>, Void>).invoke(with: self)
}
}
}

View File

@@ -39,7 +39,7 @@ import SwiftUI
// MARK: - LiveObject
@dynamicMemberLookup
public final class LiveObject<O: DynamicObject>: Identifiable, Hashable {
public final class LiveObject<O: DynamicObject>: ObjectRepresentation, Hashable {
// MARK: Public
@@ -51,19 +51,60 @@ public final class LiveObject<O: DynamicObject>: Identifiable, Hashable {
return self.lazySnapshot
}
public private(set) lazy var object: O = self.context.fetchExisting(self.id)!
public private(set) lazy var object: O = self.context.fetchExisting(self.objectID)!
public func addObserver<T: AnyObject>(_ observer: T, _ callback: @escaping (LiveObject<O>) -> Void) {
self.observers.setObject(
Internals.Closure(callback),
forKey: observer
)
}
public func removeObserver<T: AnyObject>(_ observer: T) {
self.observers.removeObject(forKey: observer)
}
deinit {
// MARK: Identifiable
self.observers.removeAllObjects()
}
// MARK: ObjectRepresentation
public let id: O.ObjectID
public typealias ObjectType = O
public static func cs_fromRaw(object: NSManagedObject) -> Self {
return self.init(
objectID: object.objectID,
context: object.managedObjectContext!
)
}
public func cs_id() -> O.ObjectID {
return self.objectID
}
public func cs_object() -> O? {
return self.context.fetchExisting(self.objectID)
}
public func cs_rawObject(in context: NSManagedObjectContext) -> NSManagedObject? {
return self.object.cs_toRaw()
}
// MARK: Equatable
public static func == (_ lhs: LiveObject, _ rhs: LiveObject) -> Bool {
return lhs.id == rhs.id
return lhs.objectID == rhs.objectID
&& lhs.context == rhs.context
}
@@ -72,23 +113,25 @@ public final class LiveObject<O: DynamicObject>: Identifiable, Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
hasher.combine(self.objectID)
hasher.combine(self.context)
}
// MARK: LiveResult
public typealias ObjectType = O
public typealias SnapshotType = ObjectSnapshot<O>
// MARK: Internal
internal convenience init(id: ID, context: NSManagedObjectContext) {
internal convenience init(objectID: O.ObjectID, context: NSManagedObjectContext) {
self.init(id: id, context: context, initializer: ObjectSnapshot<O>.init(id:context:))
self.init(
objectID: objectID,
context: context,
initializer: ObjectSnapshot<O>.init(id:context:)
)
}
@@ -96,9 +139,9 @@ public final class LiveObject<O: DynamicObject>: Identifiable, Hashable {
fileprivate let rawObjectWillChange: Any?
fileprivate init(id: O.ObjectID, context: NSManagedObjectContext, initializer: @escaping (NSManagedObjectID, NSManagedObjectContext) -> ObjectSnapshot<O>) {
fileprivate init(objectID: O.ObjectID, context: NSManagedObjectContext, initializer: @escaping (NSManagedObjectID, NSManagedObjectContext) -> ObjectSnapshot<O>) {
self.id = id
self.objectID = objectID
self.context = context
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
@@ -114,7 +157,7 @@ public final class LiveObject<O: DynamicObject>: Identifiable, Hashable {
self.rawObjectWillChange = nil
}
self.$lazySnapshot.initialize({ initializer(id, context) })
self.$lazySnapshot.initialize({ initializer(objectID, context) })
context.objectsDidChangeObserver(for: self).addObserver(self) { [weak self] (objectIDs) in
@@ -122,18 +165,36 @@ public final class LiveObject<O: DynamicObject>: Identifiable, Hashable {
return
}
self.$lazySnapshot.reset({ initializer(id, context) })
self.willChange()
self.$lazySnapshot.reset({ initializer(objectID, context) })
self.notifyObservers()
self.didChange()
}
}
// MARK: Private
private let objectID: O.ObjectID
private let context: NSManagedObjectContext
@Internals.LazyNonmutating(uninitialized: ())
private var lazySnapshot: ObjectSnapshot<O>
private lazy var observers: NSMapTable<AnyObject, Internals.Closure<LiveObject<O>, Void>> = .weakToStrongObjects()
private func notifyObservers() {
guard let enumerator = self.observers.objectEnumerator() else {
return
}
for closure in enumerator {
(closure as! Internals.Closure<LiveObject
<O>, Void>).invoke(with: self)
}
}
}

View File

@@ -87,7 +87,7 @@ extension NSManagedObjectContext {
}
@nonobjc
internal func liveObject<D: DynamicObject>(id: NSManagedObjectID) -> LiveObject<D> {
internal func liveObject<D: DynamicObject>(objectID: NSManagedObjectID) -> LiveObject<D> {
let cache: NSMapTable<NSManagedObjectID, LiveObject<D>> = self.userInfo(for: .liveObjectsCache(D.self)) {
@@ -95,12 +95,12 @@ extension NSManagedObjectContext {
}
return Internals.with {
if let liveObject = cache.object(forKey: id) {
if let liveObject = cache.object(forKey: objectID) {
return liveObject
}
let liveObject = LiveObject<D>(id: id, context: self)
cache.setObject(liveObject, forKey: id)
let liveObject = LiveObject<D>(objectID: objectID, context: self)
cache.setObject(liveObject, forKey: objectID)
return liveObject
}
}

View File

@@ -40,12 +40,7 @@ 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<D: DynamicObject>: Equatable {
/**
The type for the object contained by the `ObjectMonitor`
*/
public typealias ObjectType = D
public final class ObjectMonitor<D: DynamicObject>: ObjectRepresentation, Equatable {
/**
Returns the `DynamicObject` instance being observed, or `nil` if the object was already deleted.
@@ -125,6 +120,34 @@ public final class ObjectMonitor<D: DynamicObject>: Equatable {
public let userInfo = UserInfo()
// MARK: ObjectRepresentation
public typealias ObjectType = D
public static func cs_fromRaw(object: NSManagedObject) -> Self {
return self.init(
context: object.managedObjectContext!,
objectID: object.objectID
)
}
public func cs_id() -> ObjectType.ObjectID {
return self.objectID
}
public func cs_object() -> D? {
return self.object
}
public func cs_rawObject(in context: NSManagedObjectContext) -> NSManagedObject? {
return context.fetchExisting(self.objectID)
}
// MARK: Equatable
public static func == (lhs: ObjectMonitor<ObjectType>, rhs: ObjectMonitor<ObjectType>) -> Bool {
@@ -158,14 +181,14 @@ public final class ObjectMonitor<D: DynamicObject>: Equatable {
// MARK: Internal
internal convenience init(dataStack: DataStack, object: ObjectType) {
internal convenience init<O: ObjectRepresentation>(dataStack: DataStack, object: O) where O.ObjectType == ObjectType {
self.init(context: dataStack.mainContext, object: object)
self.init(context: dataStack.mainContext, objectID: object.cs_id())
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: ObjectType) {
internal convenience init<O: ObjectRepresentation>(unsafeTransaction: UnsafeDataTransaction, object: O) where O.ObjectType == ObjectType {
self.init(context: unsafeTransaction.context, object: object)
self.init(context: unsafeTransaction.context, objectID: object.cs_id())
}
internal func registerObserver<U: AnyObject>(_ observer: U, willChangeObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<ObjectType>, _ object: ObjectType) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<ObjectType>, _ object: ObjectType) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<ObjectType>, _ object: ObjectType, _ changedPersistentKeys: Set<String>) -> Void) {
@@ -250,6 +273,7 @@ public final class ObjectMonitor<D: DynamicObject>: Equatable {
// MARK: Private
private let objectID: ObjectType.ObjectID
private let fetchedResultsController: Internals.CoreStoreFetchedResultsController
private let fetchedResultsControllerDelegate: Internals.FetchedResultsControllerDelegate
private var lastCommittedAttributes = [String: NSObject]()
@@ -258,9 +282,8 @@ public final class ObjectMonitor<D: DynamicObject>: Equatable {
private var didDeleteObjectKey: Void?
private var didUpdateObjectKey: Void?
private init(context: NSManagedObjectContext, object: ObjectType) {
private init(context: NSManagedObjectContext, objectID: ObjectType.ObjectID) {
let objectID = object.cs_id()
let fetchRequest = Internals.CoreStoreFetchRequest<NSManagedObject>()
fetchRequest.entity = objectID.entity
fetchRequest.fetchLimit = 0
@@ -278,6 +301,7 @@ public final class ObjectMonitor<D: DynamicObject>: Equatable {
let fetchedResultsControllerDelegate = Internals.FetchedResultsControllerDelegate()
self.objectID = objectID
self.fetchedResultsController = fetchedResultsController
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate

View File

@@ -2,8 +2,76 @@
// ObjectRepresentation.swift
// CoreStore
//
// Created by John Estropia on 2019/10/11.
// Copyright © 2019 John Rommel Estropia. All rights reserved.
// Copyright © 2018 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 - ObjectRepresentation
/**
An object that acts as interfaces for `CoreStoreObject`s or `NSManagedObject`s
*/
public protocol ObjectRepresentation {
/**
The object type represented by this protocol
*/
associatedtype ObjectType: DynamicObject
/**
Used internally by CoreStore. Do not call directly.
*/
static func cs_fromRaw(object: NSManagedObject) -> Self
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_id() -> ObjectType.ObjectID
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_object() -> ObjectType?
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_rawObject(in context: NSManagedObjectContext) -> NSManagedObject?
}
extension NSManagedObject: ObjectRepresentation {}
extension CoreStoreObject: ObjectRepresentation {}
extension DynamicObject where Self: ObjectRepresentation {
public func cs_object() -> Self? {
return self
}
public func cs_rawObject(in context: NSManagedObjectContext) -> NSManagedObject? {
return context.fetchExisting(self.cs_id())
}
}

View File

@@ -97,51 +97,35 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
return super.edit(into, objectID)
}
/**
Deletes a specified `NSManagedObject` or `CoreStoreObject`.
- parameter object: the `NSManagedObject` or `CoreStoreObject` type to be deleted
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by series of `ObjectRepresentation`s.
- parameter object: the `ObjectRepresentation` representing an `NSManagedObject` or `CoreStoreObject` to be deleted
- parameter objects: other `ObjectRepresentation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public override func delete<D: DynamicObject>(_ object: D?) {
Internals.assert(
!self.isCommitted,
"Attempted to delete an entity of type \(Internals.typeName(object)) from an already committed \(Internals.typeName(self))."
)
super.delete(object)
}
/**
Deletes the specified `DynamicObject`s.
- parameter object1: the `DynamicObject` to be deleted
- parameter object2: another `DynamicObject` to be deleted
- parameter objects: other `DynamicObject`s to be deleted
*/
public override func delete<D: DynamicObject>(_ object1: D?, _ object2: D?, _ objects: D?...) {
public override func delete<O: ObjectRepresentation>(_ object: O?, _ objects: O?...) {
Internals.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
)
super.delete(([object1, object2] + objects).compactMap { $0 })
super.delete(([object] + objects).compactMap { $0 })
}
/**
Deletes the specified `DynamicObject`s.
- parameter objects: the `DynamicObject`s to be deleted
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by an `ObjectRepresenation`.
- parameter objects: the `ObjectRepresenation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public override func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: DynamicObject {
public override func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: ObjectRepresentation {
Internals.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
)
super.delete(objects)
}

View File

@@ -33,12 +33,12 @@ import CoreData
extension UnsafeDataTransaction {
/**
Creates a `ObjectMonitor` for the specified `DynamicObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `DynamicObject`.
Creates a `ObjectMonitor` for the specified `ObjectRepresentation`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `DynamicObject`.
- parameter object: the `DynamicObject` to observe changes from
- parameter object: the `ObjectRepresentation` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
public func monitorObject<D>(_ object: D) -> ObjectMonitor<D> {
public func monitorObject<O: ObjectRepresentation>(_ object: O) -> ObjectMonitor<O.ObjectType> {
return ObjectMonitor(
unsafeTransaction: self,