From 12c58e395594481fd79ccd979d0613a6f715a0ec Mon Sep 17 00:00:00 2001 From: John Estropia Date: Sat, 12 Oct 2019 10:02:00 +0900 Subject: [PATCH] improved caching in utility methods --- CoreStoreDemo/CoreStoreDemo/Info.plist | 2 +- .../SwiftUI Demo/SwiftUIView.swift | 3 +- .../DiffableDataSourceSnapshotProtocol.swift | 28 +++++ ...Internals.DiffableDataSourceSnapshot.swift | 114 +++++++++++++++--- ...edDiffableDataSourceSnapshotDelegate.swift | 46 +++---- ...Internals.SharedNotificationObserver.swift | 65 ++++++++-- Sources/ListSnapshot.swift | 38 +++--- Sources/LiveList.swift | 6 +- Sources/LiveObject.swift | 33 ++--- .../NSManagedObjectContext+CoreStore.swift | 84 ++++++++++--- 10 files changed, 307 insertions(+), 112 deletions(-) diff --git a/CoreStoreDemo/CoreStoreDemo/Info.plist b/CoreStoreDemo/CoreStoreDemo/Info.plist index c99470e..3f0a53f 100644 --- a/CoreStoreDemo/CoreStoreDemo/Info.plist +++ b/CoreStoreDemo/CoreStoreDemo/Info.plist @@ -51,6 +51,6 @@ UIViewControllerBasedStatusBarAppearance UIUserInterfaceStyle - light + Light diff --git a/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift b/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift index fdf851e..ccad57d 100644 --- a/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift +++ b/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift @@ -39,7 +39,7 @@ struct SwiftUIView: View { var body: some View { NavigationView { List { - ForEach(palettes.sections, id: \.self) { (sectionID) in + ForEach(palettes.sectionIdentifiers, id: \.self) { (sectionID) in Section(header: Text(sectionID)) { ForEach(self.palettes[section: sectionID], id: \.self) { palette in NavigationLink( @@ -118,7 +118,6 @@ struct SwiftUIView: View { self.needsShowAlert = true } } - .colorScheme(.dark) } @State diff --git a/Sources/DiffableDataSourceSnapshotProtocol.swift b/Sources/DiffableDataSourceSnapshotProtocol.swift index 46872bc..34195b6 100644 --- a/Sources/DiffableDataSourceSnapshotProtocol.swift +++ b/Sources/DiffableDataSourceSnapshotProtocol.swift @@ -31,4 +31,32 @@ import CoreData internal protocol DiffableDataSourceSnapshotProtocol { + init() + + var numberOfItems: Int { get } + var numberOfSections: Int { get } + var sectionIdentifiers: [String] { get } + var itemIdentifiers: [NSManagedObjectID] { get } + + func numberOfItems(inSection identifier: String) -> Int + func itemIdentifiers(inSection identifier: String) -> [NSManagedObjectID] + func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> String? + func indexOfItem(_ identifier: NSManagedObjectID) -> Int? + func indexOfSection(_ identifier: String) -> Int? + + mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: String?) + mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID) + mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID) + mutating func deleteItems(_ identifiers: [NSManagedObjectID]) + mutating func deleteAllItems() + mutating func moveItem(_ identifier: NSManagedObjectID, beforeItem toIdentifier: NSManagedObjectID) + mutating func moveItem(_ identifier: NSManagedObjectID, afterItem toIdentifier: NSManagedObjectID) + mutating func reloadItems(_ identifiers: [NSManagedObjectID]) + mutating func appendSections(_ identifiers: [String]) + mutating func insertSections(_ identifiers: [String], beforeSection toIdentifier: String) + mutating func insertSections(_ identifiers: [String], afterSection toIdentifier: String) + mutating func deleteSections(_ identifiers: [String]) + mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String) + mutating func moveSection(_ identifier: String, afterSection toIdentifier: String) + mutating func reloadSections(_ identifiers: [String]) } diff --git a/Sources/Internals.DiffableDataSourceSnapshot.swift b/Sources/Internals.DiffableDataSourceSnapshot.swift index deb0a74..15bcc98 100644 --- a/Sources/Internals.DiffableDataSourceSnapshot.swift +++ b/Sources/Internals.DiffableDataSourceSnapshot.swift @@ -40,24 +40,26 @@ import AppKit extension Internals { - // MARK: - DiffableDataSourceSnapshot // Implementation based on https://github.com/ra1028/DiffableDataSources - internal struct DiffableDataSourceSnapshot { + internal struct DiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol { // MARK: Internal - init() { - - self.structure = .init() - } - init(sections: [NSFetchedResultsSectionInfo]) { self.structure = .init(sections: sections) } + + // MARK: DiffableDataSourceSnapshotProtocol + + init() { + + self.structure = .init() + } + var numberOfItems: Int { return self.structure.allItemIDs.count @@ -68,37 +70,37 @@ extension Internals { return self.structure.allSectionIDs.count } - var allSectionIDs: [String] { + var sectionIdentifiers: [String] { return self.structure.allSectionIDs } - var allItemIDs: [NSManagedObjectID] { + var itemIdentifiers: [NSManagedObjectID] { return self.structure.allItemIDs } func numberOfItems(inSection identifier: String) -> Int { - return self.itemIDs(inSection: identifier).count + return self.itemIdentifiers(inSection: identifier).count } - func itemIDs(inSection identifier: String) -> [NSManagedObjectID] { + func itemIdentifiers(inSection identifier: String) -> [NSManagedObjectID] { return self.structure.items(in: identifier) } - func sectionIDs(containingItem identifier: NSManagedObjectID) -> String? { + func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> String? { return self.structure.section(containing: identifier) } - func indexOfItemID(_ identifier: NSManagedObjectID) -> Int? { + func indexOfItem(_ identifier: NSManagedObjectID) -> Int? { return self.structure.allItemIDs.firstIndex(of: identifier) } - func indexOfSectionID(_ identifier: String) -> Int? { + func indexOfSection(_ identifier: String) -> Int? { return self.structure.allSectionIDs.firstIndex(of: identifier) } @@ -138,9 +140,9 @@ extension Internals { self.structure.move(itemID: identifier, after: toIdentifier) } - mutating func reloadItems(_ identifiers: S, nextStateTag: UUID) where S.Element == NSManagedObjectID { + mutating func reloadItems(_ identifiers: [NSManagedObjectID]) { - self.structure.update(itemIDs: identifiers, nextStateTag: nextStateTag) + self.structure.update(itemIDs: identifiers) } mutating func appendSections(_ identifiers: [String]) { @@ -173,9 +175,9 @@ extension Internals { self.structure.move(sectionID: identifier, after: toIdentifier) } - mutating func reloadSections(_ identifiers: S, nextStateTag: UUID) where S.Element == String { + mutating func reloadSections(_ identifiers: [String]) { - self.structure.update(sectionIDs: identifiers, nextStateTag: nextStateTag) + self.structure.update(sectionIDs: identifiers) } @@ -388,7 +390,7 @@ extension Internals { .insert(removed, at: itemIndex) } - mutating func update(itemIDs: S, nextStateTag: UUID) where S.Element == NSManagedObjectID { + mutating func update(itemIDs: S) where S.Element == NSManagedObjectID { let itemPositionMap = self.itemPositionMap() for itemID in itemIDs { @@ -464,7 +466,7 @@ extension Internals { self.sections.insert(removed, at: sectionIndex) } - mutating func update(sectionIDs: S, nextStateTag: UUID) where S.Element == String { + mutating func update(sectionIDs: S) where S.Element == String { for sectionID in sectionIDs { @@ -590,4 +592,76 @@ extension Internals { } +// MARK: - NSDiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol + +@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) +extension NSDiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol where SectionIdentifierType == NSString, ItemIdentifierType == NSManagedObjectID { + + internal var sectionIdentifiers: [String] { + + return self.sectionIdentifiers as [NSString] as [String] + } + + internal func numberOfItems(inSection identifier: String) -> Int { + + return self.numberOfItems(inSection: identifier as NSString) + } + + internal func itemIdentifiers(inSection identifier: String) -> [NSManagedObjectID] { + + return self.itemIdentifiers(inSection: identifier as NSString) + } + + internal func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> String? { + + return self.sectionIdentifier(containingItem: identifier) as NSString? as String? + } + + internal func indexOfSection(_ identifier: String) -> Int? { + + return self.indexOfSection(identifier as NSString) + } + + internal mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: String?) { + + self.appendItems(identifiers, toSection: sectionIdentifier as NSString?) + } + + internal mutating func appendSections(_ identifiers: [String]) { + + self.appendSections(identifiers as [NSString]) + } + + internal mutating func insertSections(_ identifiers: [String], beforeSection toIdentifier: String) { + + self.insertSections(identifiers as [NSString], beforeSection: toIdentifier as NSString) + } + + internal mutating func insertSections(_ identifiers: [String], afterSection toIdentifier: String) { + + return self.insertSections(identifiers as [NSString], afterSection: toIdentifier as NSString) + } + + internal mutating func deleteSections(_ identifiers: [String]) { + + self.deleteSections(identifiers as [NSString]) + } + + internal mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String) { + + self.moveSection(identifier as NSString, beforeSection: toIdentifier as NSString) + } + + internal mutating func moveSection(_ identifier: String, afterSection toIdentifier: String) { + + self.moveSection(identifier as NSString, afterSection: toIdentifier as NSString) + } + + internal mutating func reloadSections(_ identifiers: [String]) { + + self.reloadSections(identifiers as [NSString]) + } +} + + #endif diff --git a/Sources/Internals.FetchedDiffableDataSourceSnapshotDelegate.swift b/Sources/Internals.FetchedDiffableDataSourceSnapshotDelegate.swift index a49e902..5a1ad65 100644 --- a/Sources/Internals.FetchedDiffableDataSourceSnapshotDelegate.swift +++ b/Sources/Internals.FetchedDiffableDataSourceSnapshotDelegate.swift @@ -41,7 +41,7 @@ import AppKit internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject { - func controller(_ controller: NSFetchedResultsController, didChangContentWith snapshot: Internals.DiffableDataSourceSnapshot) + func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: DiffableDataSourceSnapshotProtocol) } @@ -75,14 +75,14 @@ extension Internals { internal func initialFetch() { -// #if canImport(UIKit) || canImport(AppKit) -// -// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) { -// -// return -// } -// -// #endif + #if canImport(UIKit) || canImport(AppKit) + + if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) { + + return + } + + #endif guard let fetchedResultsController = self.fetchedResultsController else { @@ -94,26 +94,26 @@ extension Internals { // MARK: NSFetchedResultsControllerDelegate -// #if canImport(UIKit) || canImport(AppKit) -// -// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) -// @objc -// dynamic func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { -// -// self.handler?.controller( -// controller, -// didChangContentWith: snapshot as NSDiffableDataSourceSnapshot -// ) -// } -// -// #endif + #if canImport(UIKit) || canImport(AppKit) + + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) + @objc + dynamic func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { + + self.handler?.controller( + controller, + didChangeContentWith: snapshot as NSDiffableDataSourceSnapshot + ) + } + + #endif @objc dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController) { self.handler?.controller( controller, - didChangContentWith: Internals.DiffableDataSourceSnapshot( + didChangeContentWith: Internals.DiffableDataSourceSnapshot( sections: controller.sections ?? [] ) ) diff --git a/Sources/Internals.SharedNotificationObserver.swift b/Sources/Internals.SharedNotificationObserver.swift index c97aa4c..e724ab5 100644 --- a/Sources/Internals.SharedNotificationObserver.swift +++ b/Sources/Internals.SharedNotificationObserver.swift @@ -25,32 +25,83 @@ import Foundation - // MARK: - Internal extension Internals { // MARK: - SharedNotificationObserver - internal final class SharedNotificationObserver { + internal final class SharedNotificationObserver { // MARK: Internal - let observer: NSObjectProtocol - - init(notificationName: Notification.Name, object: Any?, queue: OperationQueue? = nil, closure: @escaping (_ note: Notification) -> Void) { + internal init(notificationName: Notification.Name, object: Any?, queue: OperationQueue? = nil, sharedValue: @escaping (_ note: Notification) -> T) { self.observer = NotificationCenter.default.addObserver( forName: notificationName, object: object, queue: queue, - using: closure + using: { [weak self] (notification) in + + guard let self = self else { + + return + } + let value = sharedValue(notification) + self.notifyObservers(value) + } ) } deinit { - NotificationCenter.default.removeObserver(self.observer) + self.observer.map(NotificationCenter.default.removeObserver(_:)) + } + + internal func addObserver(_ observer: U, closure: @escaping (T) -> Void) { + + self.observers.setObject(Closure(closure), forKey: observer) + } + + + // MARK: Private + + private var observer: NSObjectProtocol! + private let observers: NSMapTable = .weakToStrongObjects() + + private func notifyObservers(_ sharedValue: T) { + + guard let enumerator = self.observers.objectEnumerator() else { + + return + } + for closure in enumerator { + + (closure as! Closure).invoke(with: sharedValue) + } + } + + + // MARK: - Closure + + fileprivate final class Closure { + + // MARK: FilePrivate + + fileprivate init(_ closure: @escaping (T) -> Void) { + + self.closure = closure + } + + fileprivate func invoke(with argument: T) { + + self.closure(argument) + } + + + // MARK: Private + + private let closure: (T) -> Void } } } diff --git a/Sources/ListSnapshot.swift b/Sources/ListSnapshot.swift index 51b985d..4a03c73 100644 --- a/Sources/ListSnapshot.swift +++ b/Sources/ListSnapshot.swift @@ -46,7 +46,7 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec public subscript(indices indices: S) -> [LiveObject] where S.Element == Index { let context = self.context! - let itemIDs = self.diffableSnapshot.allItemIDs + let itemIDs = self.diffableSnapshot.itemIdentifiers return indices.map { position in let itemID = itemIDs[position] @@ -57,7 +57,7 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec public subscript(section sectionID: SectionID) -> [LiveObject] { let context = self.context! - let itemIDs = self.diffableSnapshot.itemIDs(inSection: sectionID) + let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID) return itemIDs.map { return LiveObject(id: $0, context: context) @@ -67,7 +67,7 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec public subscript(section sectionID: SectionID, itemIndices itemIndices: S) -> [LiveObject] where S.Element == Int { let context = self.context! - let itemIDs = self.diffableSnapshot.itemIDs(inSection: sectionID) + let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID) return itemIndices.map { position in let itemID = itemIDs[position] @@ -85,14 +85,14 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec return self.diffableSnapshot.numberOfSections } - public var sectionIDs: [SectionID] { + public var sectionIdentifiers: [SectionID] { - return self.diffableSnapshot.allSectionIDs + return self.diffableSnapshot.sectionIdentifiers } public var itemIdentifiers: [ItemID] { - return self.diffableSnapshot.allItemIDs + return self.diffableSnapshot.itemIdentifiers } public func numberOfItems(inSection identifier: SectionID) -> Int { @@ -102,28 +102,28 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec public func itemIdentifiers(inSection identifier: SectionID) -> [ItemID] { - return self.diffableSnapshot.itemIDs(inSection: identifier) + return self.diffableSnapshot.itemIdentifiers(inSection: identifier) } public func itemIdentifiers(inSection identifier: SectionID, atIndices indices: IndexSet) -> [ItemID] { - let itemIDs = self.diffableSnapshot.itemIDs(inSection: identifier) + let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: identifier) return indices.map({ itemIDs[$0] }) } public func sectionIdentifier(containingItem identifier: ItemID) -> SectionID? { - return self.diffableSnapshot.sectionIDs(containingItem: identifier) + return self.diffableSnapshot.sectionIdentifier(containingItem: identifier) } public func indexOfItem(_ identifier: ItemID) -> Index? { - return self.diffableSnapshot.indexOfItemID(identifier) + return self.diffableSnapshot.indexOfItem(identifier) } public func indexOfSection(_ identifier: SectionID) -> Int? { - return self.diffableSnapshot.indexOfSectionID(identifier) + return self.diffableSnapshot.indexOfSection(identifier) } public mutating func appendItems(_ identifiers: [ItemID], toSection sectionIdentifier: SectionID? = nil) { @@ -163,7 +163,7 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec public mutating func reloadItems(_ identifiers: [ItemID]) { - self.diffableSnapshot.reloadItems(identifiers, nextStateTag: .init()) + self.diffableSnapshot.reloadItems(identifiers) } public mutating func appendSections(_ identifiers: [SectionID]) { @@ -198,7 +198,7 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec public mutating func reloadSections(_ identifiers: [SectionID]) { - self.diffableSnapshot.reloadSections(identifiers, nextStateTag: .init()) + self.diffableSnapshot.reloadSections(identifiers) } @@ -211,18 +211,18 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec public var startIndex: Index { - return self.diffableSnapshot.allItemIDs.startIndex + return self.diffableSnapshot.itemIdentifiers.startIndex } public var endIndex: Index { - return self.diffableSnapshot.allItemIDs.endIndex + return self.diffableSnapshot.itemIdentifiers.endIndex } public subscript(position: Index) -> Element { let context = self.context! - let itemID = self.diffableSnapshot.allItemIDs[position] + let itemID = self.diffableSnapshot.itemIdentifiers[position] return LiveObject(id: itemID, context: context) } @@ -254,11 +254,11 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec internal init() { - self.diffableSnapshot = .init() + self.diffableSnapshot = Internals.DiffableDataSourceSnapshot() self.context = nil } - internal init(diffableSnapshot: Internals.DiffableDataSourceSnapshot, context: NSManagedObjectContext) { + internal init(diffableSnapshot: DiffableDataSourceSnapshotProtocol, context: NSManagedObjectContext) { self.diffableSnapshot = diffableSnapshot self.context = context @@ -270,5 +270,5 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec private let id: UUID = .init() private let context: NSManagedObjectContext? - private var diffableSnapshot: Internals.DiffableDataSourceSnapshot + private var diffableSnapshot: DiffableDataSourceSnapshotProtocol } diff --git a/Sources/LiveList.swift b/Sources/LiveList.swift index da455aa..35fc7c5 100644 --- a/Sources/LiveList.swift +++ b/Sources/LiveList.swift @@ -63,9 +63,9 @@ public final class LiveList: Hashable { return self.snapshot.numberOfSections } - public var sections: [SectionID] { + public var sectionIdentifiers: [SectionID] { - return self.snapshot.sectionIDs + return self.snapshot.sectionIdentifiers } public subscript(section sectionID: SectionID) -> [LiveObject] { @@ -302,7 +302,7 @@ extension LiveList: FetchedDiffableDataSourceSnapshotHandler { // MARK: FetchedDiffableDataSourceSnapshotHandler - internal func controller(_ controller: NSFetchedResultsController, didChangContentWith snapshot: Internals.DiffableDataSourceSnapshot) { + internal func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: DiffableDataSourceSnapshotProtocol) { self.snapshot = .init( diffableSnapshot: snapshot, diff --git a/Sources/LiveObject.swift b/Sources/LiveObject.swift index 2e6c937..545c8bc 100644 --- a/Sources/LiveObject.swift +++ b/Sources/LiveObject.swift @@ -114,36 +114,23 @@ public final class LiveObject: Identifiable, Hashable { self.rawObjectWillChange = nil } - self.observer = NotificationCenter.default.addObserver( - forName: .NSManagedObjectContextObjectsDidChange, - object: context, - queue: .main, - using: { [weak self] (notification) in - - guard let self = self, let userInfo = notification.userInfo else { - - return - } - let updatedObjects = (userInfo[NSUpdatedObjectsKey] as! NSSet? ?? []) - let mergedObjects = (userInfo[NSRefreshedObjectsKey] as! NSSet? ?? []) - guard mergedObjects.contains(where: { ($0 as! NSManagedObject).objectID == id }) - || updatedObjects.contains(where: { ($0 as! NSManagedObject).objectID == id }) else { - - return - } - self.$lazySnapshot.reset({ initializer(id, context) }) - self.willChange() - } - ) - self.$lazySnapshot.initialize({ initializer(id, context) }) + + context.objectsDidChangeObserver(for: self).addObserver(self) { [weak self] (objectIDs) in + + guard let self = self else { + + return + } + self.$lazySnapshot.reset({ initializer(id, context) }) + self.willChange() + } } // MARK: Private private let context: NSManagedObjectContext - private var observer: NSObjectProtocol? @Internals.LazyNonmutating(uninitialized: ()) private var lazySnapshot: ObjectSnapshot diff --git a/Sources/NSManagedObjectContext+CoreStore.swift b/Sources/NSManagedObjectContext+CoreStore.swift index ffe0b9f..10524c4 100644 --- a/Sources/NSManagedObjectContext+CoreStore.swift +++ b/Sources/NSManagedObjectContext+CoreStore.swift @@ -89,7 +89,10 @@ extension NSManagedObjectContext { @nonobjc internal func liveObject(id: NSManagedObjectID) -> LiveObject { - let cache = self.liveObjectsCache(D.self) + let cache: NSMapTable> = self.userInfo(for: .liveObjectsCache(D.self)) { + + return .strongToWeakObjects() + } return Internals.with { if let liveObject = cache.object(forKey: id) { @@ -103,27 +106,38 @@ extension NSManagedObjectContext { } @nonobjc - private func liveObjectsCache(_ objectType: D.Type) -> NSMapTable> { + internal func objectsDidChangeObserver(for observer: U) -> Internals.SharedNotificationObserver> { - let key = Internals.typeName(objectType) - if let cache = self.userInfo[key] { + return self.userInfo(for: .objectsChangeObserver(U.self)) { [unowned self] in - return cache as! NSMapTable> + return .init( + notificationName: .NSManagedObjectContextObjectsDidChange, + object: self, + queue: .main, + sharedValue: { (notification) -> Set in + + guard let userInfo = notification.userInfo else { + + return [] + } + var updatedObjectIDs: Set = [] + if let updatedObjects = userInfo[NSUpdatedObjectsKey] as? Set { + + updatedObjectIDs.formUnion(updatedObjects) + } + if let mergedObjects = userInfo[NSRefreshedObjectsKey] as? Set { + + updatedObjectIDs.formUnion(mergedObjects) + } + return updatedObjectIDs + } + ) } - let cache = NSMapTable>.strongToWeakObjects() - self.userInfo[key] = cache - return cache } // MARK: Private - private struct PropertyKeys { - - static var observerForWillSaveNotification: Void? - static var shouldCascadeSavesToParent: Void? - } - @nonobjc private var observerForWillSaveNotification: Internals.NotificationObserver? { @@ -143,5 +157,47 @@ extension NSManagedObjectContext { ) } } + + private func userInfo(for key: UserInfoKeys, initialize: @escaping () -> T) -> T { + + let keyString = key.keyString + if let value = self.userInfo[keyString] { + + return value as! T + } + let value = initialize() + self.userInfo[keyString] = value + return value + } + + + // MARK: - PropertyKeys + + private struct PropertyKeys { + + static var observerForWillSaveNotification: Void? + static var shouldCascadeSavesToParent: Void? + } + + + // MARK: - UserInfoKeys + + private enum UserInfoKeys { + + case liveObjectsCache(DynamicObject.Type) + case objectsChangeObserver(AnyObject.Type) + + var keyString: String { + + switch self { + + case .liveObjectsCache(let objectType): + return "CoreStore.liveObjectsCache(\(Internals.typeName(objectType)))" + + case .objectsChangeObserver: + return "CoreStore.objectsChangeObserver" + } + } + } }