Я реализовал обертку вокруг NSFetchedResultsController
, которая преобразует управляемые объекты в массив некоторых простых структур Swift, которые будут использоваться в качестве наблюдаемых RxSwift
. Давайте предположим, что у меня достаточно причин для создания такой оболочки вместо того, чтобы работать с NSFetchedResultsControllerDelegate
напрямую.
Проблема: я не хочу воссоздавать весь массив разделов и все элементы каждый раз, когда controllerDidChangeContent(_:)
называется, потому что это не оптимальное решение. Я хочу на самом деле внести частичные изменения в массив, изменяя элементы, которые фактически были изменены. Но похоже, что controller(_:didChange:at:for:newIndexPath:)
передает изменения, адаптированные для использования в UITableView
. Порядок применения изменений UITableView
совершенно неочевиден для меня. Поэтому, когда я пытался применить изменения к моему массиву разделов, иногда я получал ошибку index out of range
. Таким образом, я добавил некоторую проверку на выброс для индексов массива, но это не решает реальную проблему: вместо сбоев теперь иногда я дублирую элементы, что является неправильным поведением.
Может кто-нибудь объяснить мне, как правильно применить изменения от controller(_:didChange:at:for:newIndexPath:)
к массиву.
private class DelegateHandler<Item>: NSObject, NSFetchedResultsControllerDelegate {
weak var itemListObservable: RxItemListObservable<Item>?
fileprivate var optionalSections = [ItemListSection<Item?>]()
private var insertedSections = [Int]()
private var deletedSections = [Int]()
private var insertedItems = [Int: [Int]]()
private var deletedItems = [Int: [Int]]()
private var updatedItems = [Int: [Int]]()
private func clear() {
insertedSections = []
deletedSections = []
insertedItems = [:]
deletedItems = [:]
updatedItems = [:]
}
func controllerWillChangeContent(
_ controller: NSFetchedResultsController<NSFetchRequestResult>
) {
clear()
}
func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChange sectionInfo: NSFetchedResultsSectionInfo,
atSectionIndex sectionIndex: Int,
for type: NSFetchedResultsChangeType
) {
switch type {
case .insert:
insertedSections.append(sectionIndex)
case .delete:
deletedSections.append(sectionIndex)
default:
return
}
}
func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChange anObject: Any,
at indexPath: IndexPath?,
for type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?
) {
func move(from indexPath: IndexPath, to newIndexPath: IndexPath) {
insertedItems[newIndexPath.section, default: []].append(newIndexPath.item)
deletedItems[indexPath.section, default: []].append(indexPath.item)
}
switch type {
case .insert:
let path = newIndexPath!
insertedItems[path.section, default: []].append(path.item)
case .delete:
let path = indexPath!
deletedItems[path.section, default: []].append(path.item)
case .move:
move(from: indexPath!, to: newIndexPath!)
case .update:
let indexPath = indexPath!
if let newIndexPath = newIndexPath,
indexPath != newIndexPath {
move(from: indexPath, to: newIndexPath)
} else {
updatedItems[indexPath.section, default: []].append(indexPath.item)
}
@unknown default:
return
}
}
func controllerDidChangeContent(
_ controller: NSFetchedResultsController<NSFetchRequestResult>
) {
guard let itemListObservable = itemListObservable else { return }
let sections: [ItemListSection<Item?>]
do {
sections = try processChanges(
itemListObservable: itemListObservable
)
} catch {
let resultsController = itemListObservable.resultsController
sections = (resultsController.sections ?? [])
.enumerated()
.map { (sectionIndex, sectionInfo) in
let items = (0 ..< sectionInfo.numberOfObjects)
.map { index -> Item? in
let indexPath = IndexPath(item: index, section: sectionIndex)
let object = resultsController.object(at: indexPath)
return itemListObservable.converter(object)
}
return .init(name: sectionInfo.name,
indexTitle: sectionInfo.indexTitle,
items: items)
}
}
optionalSections = sections
itemListObservable.updateVisibleSections(sections)
clear()
}
private func processChanges(
itemListObservable: RxItemListObservable<Item>
) throws -> [ItemListSection<Item?>] {
var sections = optionalSections
for sectionIndex in insertedSections + deletedSections {
insertedItems[sectionIndex] = nil
deletedItems[sectionIndex] = nil
updatedItems[sectionIndex] = nil
}
// Reload items
for (sectionIndex, itemIndexes) in updatedItems {
for itemIndex in itemIndexes {
let indexPath = IndexPath(item: itemIndex, section: sectionIndex)
let object = itemListObservable.resultsController.object(at: indexPath)
let item: Item? = itemListObservable.converter(object)
try sections.checkAccess(at: sectionIndex)
try sections[sectionIndex].items.checkAccess(at: itemIndex)
sections[sectionIndex].items[itemIndex] = item
}
}
// Delete items
for (sectionIndex, itemIndexes) in deletedItems {
for itemIndex in itemIndexes.sorted(by: >) {
try sections.checkAccess(at: sectionIndex)
try sections[sectionIndex].items.checkAccess(at: itemIndex)
sections[sectionIndex].items.remove(at: itemIndex)
}
}
// Delete sections
for sectionIndex in deletedSections.sorted(by: >) {
try sections.checkAccess(at: sectionIndex)
sections.remove(at: sectionIndex)
}
// Insert sections
for sectionIndex in insertedSections.sorted(by: <) {
guard let sectionInfo = itemListObservable
.resultsController
.sections?[sectionIndex] else {
continue
}
let items = (0 ..< sectionInfo.numberOfObjects).map { itemIndex -> Item? in
let indexPath = IndexPath(item: itemIndex, section: sectionIndex)
let object = itemListObservable.resultsController.object(at: indexPath)
return itemListObservable.converter(object)
}
let section = ItemListSection(name: sectionInfo.name,
indexTitle: sectionInfo.indexTitle,
items: items)
try sections.checkInsert(at: sectionIndex)
sections.insert(section, at: sectionIndex)
}
// Insert items
for (sectionIndex, itemIndexes) in insertedItems {
for itemIndex in itemIndexes.sorted(by: <) {
let indexPath = IndexPath(item: itemIndex, section: sectionIndex)
let object = itemListObservable.resultsController.object(at: indexPath)
let item: Item? = itemListObservable.converter(object)
try sections.checkAccess(at: sectionIndex)
try sections[sectionIndex].items.checkInsert(at: itemIndex)
sections[sectionIndex].items.insert(item, at: itemIndex)
}
}
return sections
}
}
// MARK: - Utils
private extension Array {
enum ArrayError: Error {
case outOfRange
}
func checkAccess(at index: Int) throws {
guard index < count else {
throw ArrayError.outOfRange
}
}
func checkInsert(at index: Int) throws {
guard index <= count else {
throw ArrayError.outOfRange
}
}
}
Обновление # 1 Я не могу использовать Diffable Data Source, потому что мне нужно поддерживать iOS 12.