improved caching in utility methods

This commit is contained in:
John Estropia
2019-10-12 10:02:00 +09:00
parent 5af0d17de4
commit 12c58e3955
10 changed files with 307 additions and 112 deletions

View File

@@ -51,6 +51,6 @@
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIUserInterfaceStyle</key>
<string>light</string>
<string>Light</string>
</dict>
</plist>

View File

@@ -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

View File

@@ -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])
}

View File

@@ -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<S: Sequence>(_ 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<S: Sequence>(_ 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<S: Sequence>(itemIDs: S, nextStateTag: UUID) where S.Element == NSManagedObjectID {
mutating func update<S: Sequence>(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<S: Sequence>(sectionIDs: S, nextStateTag: UUID) where S.Element == String {
mutating func update<S: Sequence>(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

View File

@@ -41,7 +41,7 @@ import AppKit
internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: Internals.DiffableDataSourceSnapshot)
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, 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<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
//
// self.handler?.controller(
// controller,
// didChangContentWith: snapshot as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
// )
// }
//
// #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<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
self.handler?.controller(
controller,
didChangeContentWith: snapshot as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
)
}
#endif
@objc
dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.handler?.controller(
controller,
didChangContentWith: Internals.DiffableDataSourceSnapshot(
didChangeContentWith: Internals.DiffableDataSourceSnapshot(
sections: controller.sections ?? []
)
)

View File

@@ -25,32 +25,83 @@
import Foundation
// MARK: - Internal
extension Internals {
// MARK: - SharedNotificationObserver
internal final class SharedNotificationObserver {
internal final class SharedNotificationObserver<T> {
// 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<U: AnyObject>(_ observer: U, closure: @escaping (T) -> Void) {
self.observers.setObject(Closure(closure), forKey: observer)
}
// MARK: Private
private var observer: NSObjectProtocol!
private let observers: NSMapTable<AnyObject, Closure> = .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
}
}
}

View File

@@ -46,7 +46,7 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
public subscript<S: Sequence>(indices indices: S) -> [LiveObject<O>] 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<O: DynamicObject>: SnapshotResult, RandomAccessCollec
public subscript(section sectionID: SectionID) -> [LiveObject<O>] {
let context = self.context!
let itemIDs = self.diffableSnapshot.itemIDs(inSection: sectionID)
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
return itemIDs.map {
return LiveObject<O>(id: $0, context: context)
@@ -67,7 +67,7 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
public subscript<S: Sequence>(section sectionID: SectionID, itemIndices itemIndices: S) -> [LiveObject<O>] 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<O: DynamicObject>: 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<O: DynamicObject>: 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<O: DynamicObject>: 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<O: DynamicObject>: 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<O: DynamicObject>: 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<O>(id: itemID, context: context)
}
@@ -254,11 +254,11 @@ public struct ListSnapshot<O: DynamicObject>: 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<O: DynamicObject>: SnapshotResult, RandomAccessCollec
private let id: UUID = .init()
private let context: NSManagedObjectContext?
private var diffableSnapshot: Internals.DiffableDataSourceSnapshot
private var diffableSnapshot: DiffableDataSourceSnapshotProtocol
}

View File

@@ -63,9 +63,9 @@ public final class LiveList<O: DynamicObject>: 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<O>] {
@@ -302,7 +302,7 @@ extension LiveList: FetchedDiffableDataSourceSnapshotHandler {
// MARK: FetchedDiffableDataSourceSnapshotHandler
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: Internals.DiffableDataSourceSnapshot) {
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: DiffableDataSourceSnapshotProtocol) {
self.snapshot = .init(
diffableSnapshot: snapshot,

View File

@@ -114,36 +114,23 @@ public final class LiveObject<O: DynamicObject>: 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<O>

View File

@@ -89,7 +89,10 @@ extension NSManagedObjectContext {
@nonobjc
internal func liveObject<D: DynamicObject>(id: NSManagedObjectID) -> LiveObject<D> {
let cache = self.liveObjectsCache(D.self)
let cache: NSMapTable<NSManagedObjectID, LiveObject<D>> = 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<D: DynamicObject>(_ objectType: D.Type) -> NSMapTable<NSManagedObjectID, LiveObject<D>> {
internal func objectsDidChangeObserver<U: AnyObject>(for observer: U) -> Internals.SharedNotificationObserver<Set<NSManagedObjectID>> {
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<NSManagedObjectID, LiveObject<D>>
return .init(
notificationName: .NSManagedObjectContextObjectsDidChange,
object: self,
queue: .main,
sharedValue: { (notification) -> Set<NSManagedObjectID> in
guard let userInfo = notification.userInfo else {
return []
}
var updatedObjectIDs: Set<NSManagedObjectID> = []
if let updatedObjects = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObjectID> {
updatedObjectIDs.formUnion(updatedObjects)
}
if let mergedObjects = userInfo[NSRefreshedObjectsKey] as? Set<NSManagedObjectID> {
updatedObjectIDs.formUnion(mergedObjects)
}
return updatedObjectIDs
}
)
}
let cache = NSMapTable<NSManagedObjectID, LiveObject<D>>.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<T>(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"
}
}
}
}