Thanks, @numist, for your response.
Ideally, I'd use the NSDiffableDataSourceSnapshot
API but it has a couple of limitations that mean it won't work for my use case:
- It's a
NSDiffableDataSourceSnapshot
and not a NSDiffableDataSourceSectionSnapshot
so it can't be composed very well. - It tightly couples Core Data to the view which isn't ideal for those of us who like to modularise our code. Ideally
NSDiffableDataSource(Section)Snapshot
would by lazily mappable which would perhaps go some ways to facilitating de-coupling Core Data from the View.
For the adapter example, if I get time I'll put one together but it's pretty easy to see for your self:
Within a NSFetchedResultsControllerDelegate
, add/remove some items from a section and then check the section counts in controllerWillChangeContent(_:)
(storing the sections within the delegate for later) then check the stored section counts again in controllerDidChangeContent(_:)
.
For NSFetchedResultsSectionInfo
to be usable as a collection view data source, you would expect the section counts of the sections stored in controllerWillChangeContent(_:)
to stay constant. However, by controllerDidChangeContent(_:)
the section counts have changed. In other words, there's some 'spooky action at a distance' occurring which means NSFetchedResultsSectionInfo
can't effectively be stored for use as part of a UICollectionViewDataSource
and performBatchUpdates
specifically.
Reproducible code:
final class SomeController: NSObject, NSFetchedResultsControllerDelegate {
private var willChangeSections: [NSFetchedResultsSectionInfo]?
func controllerWillChangeContent(
_ controller: NSFetchedResultsController<NSFetchRequestResult>
) {
print(#function)
if let sections = controller.sections {
print("sections will change: \(ObjectIdentifier(sections as NSArray))")
for (i, section) in sections.enumerated() {
guard let objects = section.objects else { return }
print("\t\(ObjectIdentifier(objects as NSArray)) - section \(i) count: \(section.numberOfObjects)")
}
}
self.willChangeSections = controller.sections
}
func controllerDidChangeContent(
_ controller: NSFetchedResultsController<NSFetchRequestResult>
) {
print(#function)
if let oldSections = willChangeSections {
print("sections did change (old): \(ObjectIdentifier(oldSections as NSArray))")
for (i, section) in oldSections.enumerated() {
guard let objects = section.objects else { return }
print("\t\(ObjectIdentifier(objects as NSArray)) - section \(i) count: \(section.numberOfObjects)")
}
}
if let newSections = controller.sections {
print("sections did change (new): \(ObjectIdentifier(newSections as NSArray))")
for (i, section) in newSections.enumerated() {
guard let objects = section.objects else { return }
print("\t\(ObjectIdentifier(objects as NSArray)) - section \(i) count: \(section.numberOfObjects)")
}
}
}
}
// EXPECTED:
// controllerWillChangeContent(_:)
// sections will change: ObjectIdentifier(0x0000600001019020)
// ObjectIdentifier(0x0000600001347c00) - section 0 count: 10
//
// controllerDidChangeContent(_:)
// sections did change (old): ObjectIdentifier(0x00006000010187a0)
// ObjectIdentifier(0x0000600001347aa0) - section 0 count: 10 // <--- count the same as in willChange ✅
// sections did change (new): ObjectIdentifier(0x0000600001018660)
// ObjectIdentifier(0x0000600001347b20) - section 0 count: 11
// ACTUAL:
// controllerWillChangeContent(_:)
// sections will change: ObjectIdentifier(0x0000600001019020)
// ObjectIdentifier(0x0000600001347c00) - section 0 count: 10
//
// controllerDidChangeContent(_:)
// sections did change (old): ObjectIdentifier(0x00006000010187a0)
// ObjectIdentifier(0x0000600001347aa0) - section 0 count: 11 // <--- count has updated since willChange ❌
// sections did change (new): ObjectIdentifier(0x0000600001018660)
// ObjectIdentifier(0x0000600001347b20) - section 0 count: 11