From 7938aa2447941669257c7575733b43c937b5dd9d Mon Sep 17 00:00:00 2001 From: John Estropia Date: Sat, 12 Jun 2021 11:06:43 +0900 Subject: [PATCH] Added fast index-based ListSnapshot mutators --- .../DiffableDataSourceSnapshotProtocol.swift | 31 ++- ...Internals.DiffableDataSourceSnapshot.swift | 215 +++++++++++++++++- Sources/ListSnapshot.swift | 103 ++++++++- 3 files changed, 323 insertions(+), 26 deletions(-) diff --git a/Sources/DiffableDataSourceSnapshotProtocol.swift b/Sources/DiffableDataSourceSnapshotProtocol.swift index 34195b6..578466d 100644 --- a/Sources/DiffableDataSourceSnapshotProtocol.swift +++ b/Sources/DiffableDataSourceSnapshotProtocol.swift @@ -44,19 +44,30 @@ internal protocol DiffableDataSourceSnapshotProtocol { 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 appendItems(_ identifiers: C, toSection sectionIdentifier: String?) where C.Element == NSManagedObjectID + mutating func insertItems(_ identifiers: C, beforeItem beforeIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID + mutating func insertItems(_ identifiers: C, afterItem afterIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID + mutating func deleteItems(_ identifiers: C) where C.Element == 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 reloadItems(_ identifiers: C) where C.Element == NSManagedObjectID + mutating func appendSections(_ identifiers: C) where C.Element == String + mutating func insertSections(_ identifiers: C, beforeSection toIdentifier: String) where C.Element == String + mutating func insertSections(_ identifiers: C, afterSection toIdentifier: String) where C.Element == String + mutating func deleteSections(_ identifiers: C) where C.Element == String mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String) mutating func moveSection(_ identifier: String, afterSection toIdentifier: String) - mutating func reloadSections(_ identifiers: [String]) + mutating func reloadSections(_ identifiers: C) where C.Element == String + + mutating func unsafeAppendItems(_ identifiers: C, toSectionAt sectionIndex: Int) where C.Element == NSManagedObjectID + mutating func unsafeInsertItems(_ identifiers: C, at indexPath: IndexPath) where C.Element == NSManagedObjectID + mutating func unsafeDeleteItems(at indexPaths: C) where C.Element == IndexPath + mutating func unsafeMoveItem(at indexPath: IndexPath, to newIndexPath: IndexPath) + mutating func unsafeReloadItems(at indexPaths: C) where C.Element == IndexPath + mutating func unsafeInsertSections(_ identifiers: C, at sectionIndex: Int) where C.Element == String + mutating func unsafeDeleteSections(at sectionIndices: C) where C.Element == Int + mutating func unsafeMoveSection(at sectionIndex: Int, to newSectionIndex: Int) + mutating func unsafeReloadSections(at sectionIndices: C) where C.Element == Int + } diff --git a/Sources/Internals.DiffableDataSourceSnapshot.swift b/Sources/Internals.DiffableDataSourceSnapshot.swift index 80618eb..9c75dcb 100644 --- a/Sources/Internals.DiffableDataSourceSnapshot.swift +++ b/Sources/Internals.DiffableDataSourceSnapshot.swift @@ -200,7 +200,7 @@ extension Internals { self.structure.insert(itemIDs: identifiers, after: afterIdentifier) } - mutating func deleteItems(_ identifiers: S) where S.Element == NSManagedObjectID { + mutating func deleteItems(_ identifiers: C) where C.Element == NSManagedObjectID { self.structure.remove(itemIDs: identifiers) } @@ -220,7 +220,7 @@ extension Internals { self.structure.move(itemID: identifier, after: toIdentifier) } - mutating func reloadItems(_ identifiers: S) where S.Element == NSManagedObjectID { + mutating func reloadItems(_ identifiers: C) where C.Element == NSManagedObjectID { self.structure.update(itemIDs: identifiers) } @@ -240,7 +240,7 @@ extension Internals { self.structure.insert(sectionIDs: identifiers, after: toIdentifier) } - mutating func deleteSections(_ identifiers: S) where S.Element == String { + mutating func deleteSections(_ identifiers: C) where C.Element == String { self.structure.remove(sectionIDs: identifiers) } @@ -255,10 +255,55 @@ extension Internals { self.structure.move(sectionID: identifier, after: toIdentifier) } - mutating func reloadSections(_ identifiers: S) where S.Element == String { + mutating func reloadSections(_ identifiers: C) where C.Element == String { self.structure.update(sectionIDs: identifiers) } + + mutating func unsafeAppendItems(_ identifiers: C, toSectionAt sectionIndex: Int) where C.Element == NSManagedObjectID { + + self.structure.unsafeAppend(identifiers, toSectionAt: sectionIndex) + } + + mutating func unsafeInsertItems(_ identifiers: C, at indexPath: IndexPath) where C.Element == NSManagedObjectID { + + self.structure.unsafeInsert(itemIDs: identifiers, at: indexPath) + } + + mutating func unsafeDeleteItems(at indexPaths: C) where C.Element == IndexPath { + + self.structure.unsafeRemove(itemsAt: indexPaths) + } + + mutating func unsafeMoveItem(at indexPath: IndexPath, to newIndexPath: IndexPath) { + + self.structure.unsafeMove(itemAt: indexPath, to: newIndexPath) + } + + mutating func unsafeReloadItems(at indexPaths: C) where C.Element == IndexPath { + + self.structure.unsafeUpdate(itemsAt: indexPaths) + } + + mutating func unsafeInsertSections(_ identifiers: C, at sectionIndex: Int) where C.Element == String { + + self.structure.unsafeInsert(identifiers, at: sectionIndex) + } + + mutating func unsafeDeleteSections(at sectionIndices: C) where C.Element == Int { + + self.structure.unsafeRemove(sectionsAt: sectionIndices) + } + + mutating func unsafeMoveSection(at sectionIndex: Int, to newSectionIndex: Int) { + + self.structure.unsafeMove(sectionAt: sectionIndex, to: newSectionIndex) + } + + mutating func unsafeReloadSections(at sectionIndices: C) where C.Element == Int { + + self.structure.unsafeUpdate(sectionsAt: sectionIndices) + } // MARK: Private @@ -432,13 +477,23 @@ extension Internals { } return self.sections[sectionIndex].elements.map({ $0.differenceIdentifier }) } + + func unsafeItem(at indexPath: IndexPath) -> NSManagedObjectID { + + return self.sections[indexPath.section] + .elements[indexPath.item] + .differenceIdentifier + } func section(containing itemID: NSManagedObjectID) -> String? { return self.itemPositionMap(itemID)?.section.differenceIdentifier } - mutating func append(itemIDs: C, to sectionID: String?) where C.Element == NSManagedObjectID { + mutating func append( + itemIDs: C, + to sectionID: String? + ) where C.Element == NSManagedObjectID { let index: Array
.Index if let sectionID = sectionID { @@ -461,8 +516,30 @@ extension Internals { let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) }) self.sections[index].elements.append(contentsOf: items) } + + mutating func unsafeAppend( + _ itemIDs: C, + toSectionAt sectionIndex: Int? + ) where C.Element == NSManagedObjectID { + + let index: Array
.Index + if let sectionIndex = sectionIndex { - mutating func insert(itemIDs: C, before beforeItemID: NSManagedObjectID) where C.Element == NSManagedObjectID { + index = sectionIndex + } + else { + + let section = self.sections + index = section.index(before: section.endIndex) + } + let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) }) + self.sections[index].elements.append(contentsOf: items) + } + + mutating func insert( + itemIDs: C, + before beforeItemID: NSManagedObjectID + ) where C.Element == NSManagedObjectID { guard let itemPosition = self.itemPositionMap(beforeItemID) else { @@ -473,7 +550,10 @@ extension Internals { .insert(contentsOf: items, at: itemPosition.itemRelativeIndex) } - mutating func insert(itemIDs: C, after afterItemID: NSManagedObjectID) where C.Element == NSManagedObjectID { + mutating func insert( + itemIDs: C, + after afterItemID: NSManagedObjectID + ) where C.Element == NSManagedObjectID { guard let itemPosition = self.itemPositionMap(afterItemID) else { @@ -485,6 +565,16 @@ extension Internals { self.sections[itemPosition.sectionIndex].elements .insert(contentsOf: items, at: itemIndex) } + + mutating func unsafeInsert( + itemIDs: C, + at indexPath: IndexPath + ) where C.Element == NSManagedObjectID { + + let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) }) + self.sections[indexPath.section].elements + .insert(contentsOf: items, at: indexPath.row) + } mutating func remove(itemIDs: S) where S.Element == NSManagedObjectID { @@ -508,6 +598,23 @@ extension Internals { } } } + + mutating func unsafeRemove(itemsAt indexPaths: S) where S.Element == IndexPath { + + var removeIndexSetMap: [Int: IndexSet] = [:] + for indexPath in indexPaths { + + removeIndexSetMap[indexPath.section, default: []] + .insert(indexPath.item) + } + for (sectionIndex, removeIndexSet) in removeIndexSetMap { + + for range in removeIndexSet.rangeView.reversed() { + + self.sections[sectionIndex].elements.removeSubrange(range) + } + } + } mutating func removeAllItems() { @@ -522,7 +629,10 @@ extension Internals { self.sections.removeAll(where: { $0.elements.isEmpty }) } - mutating func move(itemID: NSManagedObjectID, before beforeItemID: NSManagedObjectID) { + mutating func move( + itemID: NSManagedObjectID, + before beforeItemID: NSManagedObjectID + ) { guard let removed = self.remove(itemID: itemID) else { @@ -536,7 +646,10 @@ extension Internals { .insert(removed, at: itemPosition.itemRelativeIndex) } - mutating func move(itemID: NSManagedObjectID, after afterItemID: NSManagedObjectID) { + mutating func move( + itemID: NSManagedObjectID, + after afterItemID: NSManagedObjectID + ) { guard let removed = self.remove(itemID: itemID) else { @@ -551,6 +664,17 @@ extension Internals { self.sections[itemPosition.sectionIndex].elements .insert(removed, at: itemIndex) } + + mutating func unsafeMove( + itemAt indexPath: IndexPath, + to newIndexPath: IndexPath + ) { + + let itemID = self.sections[indexPath.section].elements + .remove(at: indexPath.item) + self.sections[newIndexPath.section].elements + .insert(itemID, at: newIndexPath.item) + } mutating func update(itemIDs: S) where S.Element == NSManagedObjectID { @@ -568,6 +692,18 @@ extension Internals { } self.reloadedItems.formUnion(newItemIDs) } + + mutating func unsafeUpdate(itemsAt indexPaths: S) where S.Element == IndexPath { + + var newItemIDs: Set = [] + for indexPath in indexPaths { + + self.sections[indexPath.section] + .elements[indexPath.item].isReloaded = true + newItemIDs.insert(self.unsafeItem(at: indexPath)) + } + self.reloadedItems.formUnion(newItemIDs) + } mutating func append(sectionIDs: C) where C.Element == String { @@ -582,7 +718,10 @@ extension Internals { self.sections.append(contentsOf: newSections) } - mutating func insert(sectionIDs: C, before beforeSectionID: String) where C.Element == String { + mutating func insert( + sectionIDs: C, + before beforeSectionID: String + ) where C.Element == String { guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else { @@ -599,7 +738,10 @@ extension Internals { self.sections.insert(contentsOf: newSections, at: sectionIndex) } - mutating func insert(sectionIDs: C, after afterSectionID: String) where C.Element == String { + mutating func insert( + sectionIDs: C, + after afterSectionID: String + ) where C.Element == String { guard let beforeIndex = self.sectionIndex(of: afterSectionID) else { @@ -616,6 +758,22 @@ extension Internals { } self.sections.insert(contentsOf: newSections, at: sectionIndex) } + + mutating func unsafeInsert( + _ sectionIDs: C, + at sectionIndex: Int + ) where C.Element == String { + + let sectionIndexTransformer = self.sectionIndexTransformer + let newSections = sectionIDs.lazy.map { + + return Section( + differenceIdentifier: $0, + indexTitle: sectionIndexTransformer($0) + ) + } + self.sections.insert(contentsOf: newSections, at: sectionIndex) + } mutating func remove(sectionIDs: S) where S.Element == String { @@ -624,6 +782,16 @@ extension Internals { self.remove(sectionID: sectionID) } } + + mutating func unsafeRemove( + sectionsAt sectionIndices: S + ) where S.Element == Int { + + for sectionIndex in sectionIndices.sorted(by: >) { + + self.sections.remove(at: sectionIndex) + } + } mutating func move(sectionID: String, before beforeSectionID: String) { @@ -651,8 +819,21 @@ extension Internals { let sectionIndex = self.sections.index(after: beforeIndex) self.sections.insert(removed, at: sectionIndex) } + + mutating func unsafeMove( + sectionAt sectionIndex: Int, + to newSectionIndex: Int + ) { + + self.sections.move( + fromOffsets: .init(integer: sectionIndex), + toOffset: newSectionIndex + ) + } - mutating func update(sectionIDs: S) where S.Element == String { + mutating func update( + sectionIDs: S + ) where S.Element == String { for sectionID in sectionIDs { @@ -663,6 +844,16 @@ extension Internals { self.sections[sectionIndex].isReloaded = true } } + + mutating func unsafeUpdate( + sectionsAt sectionIndices: S + ) where S.Element == Int { + + for sectionIndex in sectionIndices { + + self.sections[sectionIndex].isReloaded = true + } + } // MARK: Private diff --git a/Sources/ListSnapshot.swift b/Sources/ListSnapshot.swift index 3e0390f..0441b68 100644 --- a/Sources/ListSnapshot.swift +++ b/Sources/ListSnapshot.swift @@ -471,6 +471,17 @@ public struct ListSnapshot: RandomAccessCollection, Hashable { self.diffableSnapshot.appendItems(itemIDs, toSection: sectionID) } + /** + Appends extra items to the specified section index + + - parameter itemIDs: the object identifiers for the objects to append + - parameter sectionIndex: the section index to append the items to. Specifying an invalid value will raise an exception. + */ + public mutating func appendItems(with itemIDs: C, toSectionAt sectionIndex: Int) where C.Element == ItemID { + + self.diffableSnapshot.unsafeAppendItems(itemIDs, toSectionAt: sectionIndex) + } + /** Inserts extra items before a specified item @@ -493,16 +504,37 @@ public struct ListSnapshot: RandomAccessCollection, Hashable { self.diffableSnapshot.insertItems(itemIDs, afterItem: afterItemID) } + /** + Inserts extra items at a specified index path + + - parameter itemIDs: the object identifiers for the objects to insert + - parameter indexPath: an indexPath to insert the items into. Specifying an invalid value will raise an exception. + */ + public mutating func insertItems(withIDs itemIDs: C, at indexPath: IndexPath) where C.Element == ItemID { + + self.diffableSnapshot.unsafeInsertItems(itemIDs, at: indexPath) + } + /** Deletes the specified items - parameter itemIDs: the object identifiers for the objects to delete */ - public mutating func deleteItems(withIDs itemIDs: S) where S.Element == ItemID { + public mutating func deleteItems(withIDs itemIDs: C) where C.Element == ItemID { self.diffableSnapshot.deleteItems(itemIDs) } + /** + Deletes the items at the specified index paths + + - parameter itemIndexPaths: the index paths for the objects to delete. Specifying an invalid value will raise an exception. + */ + public mutating func deleteItems(at itemIndexPaths: C) where C.Element == IndexPath { + + self.diffableSnapshot.unsafeDeleteItems(at: itemIndexPaths) + } + /** Deletes all items */ @@ -533,16 +565,37 @@ public struct ListSnapshot: RandomAccessCollection, Hashable { self.diffableSnapshot.moveItem(itemID, afterItem: afterItemID) } + /** + Moves an item at an index path to a new index path + + - parameter itemIndexPath: an index path in the list to move. Specifying an invalid value will raise an exception. + - parameter newIndexPath: the new index path to move the item into. Specifying an invalid value will raise an exception. + */ + public mutating func moveItem(at itemIndexPath: IndexPath, to newIndexPath: IndexPath) { + + self.diffableSnapshot.unsafeMoveItem(at: itemIndexPath, to: newIndexPath) + } + /** Marks the specified items as reloaded - parameter itemIDs: the object identifiers to reload */ - public mutating func reloadItems(withIDs itemIDs: S) where S.Element == ItemID { + public mutating func reloadItems(withIDs itemIDs: C) where C.Element == ItemID { self.diffableSnapshot.reloadItems(itemIDs) } + /** + Marks the specified index paths as reloaded + + - parameter itemIndexPaths: the index paths to reload. Specifying an invalid value will raise an exception. + */ + public mutating func reloadItems(at itemIndexPaths: C) where C.Element == IndexPath { + + self.diffableSnapshot.unsafeReloadItems(at: itemIndexPaths) + } + /** Appends new section identifiers to the end of the list @@ -575,16 +628,37 @@ public struct ListSnapshot: RandomAccessCollection, Hashable { self.diffableSnapshot.insertSections(sectionIDs, afterSection: afterSectionID) } + /** + Inserts new sections into an existing section index + + - parameter sectionIDs: the section identifiers for the sections to insert + - parameter sectionIndex: an existing section index to insert items into. Specifying an invalid value will raise an exception. + */ + public mutating func insertSections(_ sectionIDs: C, at sectionIndex: Int) where C.Element == String { + + self.diffableSnapshot.unsafeInsertSections(sectionIDs, at: sectionIndex) + } + /** Deletes the specified sections - parameter sectionIDs: the section identifiers for the sections to delete */ - public mutating func deleteSections(withIDs sectionIDs: S) where S.Element == SectionID { + public mutating func deleteSections(withIDs sectionIDs: C) where C.Element == SectionID { self.diffableSnapshot.deleteSections(sectionIDs) } + /** + Deletes the specified section indices + + - parameter sectionIndices: the section indices to delete. Specifying an invalid value will raise an exception. + */ + public mutating func deleteSections(at sectionIndices: C) where C.Element == Int { + + self.diffableSnapshot.unsafeDeleteSections(at: sectionIndices) + } + /** Moves a section before another specified section @@ -607,15 +681,36 @@ public struct ListSnapshot: RandomAccessCollection, Hashable { self.diffableSnapshot.moveSection(sectionID, afterSection: afterSectionID) } + /** + Moves a section at a specified index to a new index + + - parameter sectionIndex: a section index in the list to move. Specifying an invalid value will raise an exception. + - parameter newSectionIndex: the new section index to move into. Specifying an invalid value will raise an exception. + */ + public mutating func moveSection(at sectionIndex: Int, to newSectionIndex: Int) { + + self.diffableSnapshot.unsafeMoveSection(at: sectionIndex, to: newSectionIndex) + } + /** Marks the specified sections as reloaded - parameter sectionIDs: the section identifiers to reload */ - public mutating func reloadSections(withIDs sectionIDs: S) where S.Element == SectionID { + public mutating func reloadSections(withIDs sectionIDs: C) where C.Element == SectionID { self.diffableSnapshot.reloadSections(sectionIDs) } + + /** + Marks the specified section indices as reloaded + + - parameter sectionIndices: the section indices to reload. Specifying an invalid value will raise an exception. + */ + public mutating func reloadSections(at sectionIndices: C) where C.Element == Int { + + self.diffableSnapshot.unsafeReloadSections(at: sectionIndices) + } // MARK: RandomAccessCollection