mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-14 21:23:43 +01:00
WIP: ObjectRepresentable utilities
This commit is contained in:
@@ -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]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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] = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user