As of iOS 13, the easiest way to keep a UITableView in sync with a NSFetchedResultsController seems to be with snapshots.
The NSFetchedResultsController vends a snapshot reference to its delegate whenever the managedObjectContext reports additions, deletions, or updates. When using snapshots (NSDiffableDataSourceSnapshot), there is only one FRC delegate method that needs to be implemented: controller(_:didChangeContentWith:). In order to make that delegate method work, the UITableViewDiffableDataSource and the Snapshot has to be typed <String, NSManagedObjectID>.
It works mostly.
But what if the entire table needs to be updated? Using tableView.reloadData() or frc.performFetch() seems anti-pattern.
edit
I manually built a snapshot, and call apply when necessary. But since my snapshot is based on NSFetchedResultsSectionInfo objects, it seems like I'm duplicating what the FRC already has available: Hashable section titles, and Hashable NSManagedObjectIDs