Diffable data source with fetched result controller on iOS 15

Hi!

When using a diffable data source on iOS 13 and iOS 14 in combination with a fetched result controller, the didChangeContentWith method of the NSFetchedResultsController delegate return a new snapshot with inserted, deleted and moved items.

Unfortunately, in the new snapshot items that have been modified are not refreshed. That's because the system appears to compare the identifiers to determine whether a refresh is needed or not. If we change an attribute of a Core Data entity the identifier remain the same and the item in the snapshot is not refreshed. For this reason, on iOS 13 and iOS 14, we need to manually check which objects have been updated and manually refresh the corresponding items in the snapshot.

Now, it seems that on iOS 15 this is no more needed. The new snapshot I receive in the NSFetchedResultsController didChangeContentWith delegate method also contain refreshed items. So, in order to apply the new snapshot I only need to call a single line of code:

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
   guard let diffableDataSource = diffableDataSource else {return}
   diffableDataSource.apply(snapshot as NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>, animatingDifferences: true)
}

That's great, but I'm not sure if that's a feature we can count on from iOS 15 onwards. Is this new behaviour documented somewhere?

Thank you

This is correct, and you can rely on the behavior you see in iOS 15.

The way that these automatic reloads work is that NSFetchedResultsController calls reloadItems(_:) on the snapshot you receive in the controller(_:didChangeContentWith:) delegate method, passing the identifiers of items that have changed. In iOS 15, there are new properties on the snapshot that allow you to inspect the identifiers that are pending reload and reconfigure.

Before iOS 15, there was a bug in the implementation of how NSDiffableDataSourceSnapshot was bridged between Swift and Objective-C, which caused the internally-stored reloaded identifiers to be lost during the bridging process (which is what happens when you bridge from NSDiffableDataSourceSnapshotReference to NSDiffableDataSourceSnapshot using the as keyword). Therefore, automatic reloads have always worked when using NSFetchedResultsController and diffable data source from Objective-C, as no bridging to/from Swift would take place. And as of iOS 15, they now work as intended in Swift as well.

Thank you Frameworks Engineer for the clarification. Just one last question for you if I can.

Diffable data source with fetched result controller is using NSManagedObjectID's to check which items need to be added / removed / updated.

When we are creating a new entitiy, for example:

let newEntry = Entry(context: managedObjectContext)

the object has a temporary id's until the store is saved.

If we create a new object, we save the store and then we create another object (and we save the store) the diffable data source will see the new created item BUT also an edit on the previous object (because the NSManagedObjectID after the save has been transformed to permanent). This is causing weird animations on a table view controller (insert + update animation instead of just an insert animation).

Please note that this behaviour is only present if you save the store right after you created the new entity.

The only way I found to fix this is to call obtainPermanentIDs right after I created the new Core Data object, like so:

let newEntry = Entry(context: managedObjectContext)
try? managedObjectContext.obtainPermanentIDs(for: [newEntry])
try? managedObjectContext.save()

This is forcing Core Data to set the permanent id on the just created object.

Is this the correct thing to do? When using diffable data sources in combination with a fetched result controller we always need to manually call obtainPermanentIDs after creating a new entity?

Thank you

Hi DaleOne, thank you for sharing such an important information. For iOS 15, do you think the mythology described in https://www.avanderlee.com/swift/diffable-data-sources-core-data/ still accurate? Do we still need to call reloadItems explicitly? Recently, we have encountered classic NSInternalInconsistencyException for some clients, by using classic performBatchUpdates. We was thinking migrate to NSDiffableDataSourceSnapshotReference. But, we aren't sure it will solve the prob. Thank you!

Two years later, I'd like to know you managed everything at the end Dale. Im about to make a project with compositional layout, diffable datasource and core data.

Diffable data source with fetched result controller on iOS 15
 
 
Q