UIKitSeniorMCQ
What is a diffable data source and why does it replace `reloadData`?
Test your knowledge:
Explanation & Code
Answer:
A diffable data source is a data source that you drive with immutable snapshots of Hashable identifiers instead of index-path callbacks, and it computes the inserts, deletes, and moves for you. It eliminates the entire class of "invalid number of rows" crashes caused by the model and the performBatchUpdates calls falling out of sync.
Code Example:
enum Section: Hashable { case pinned, all }
struct Contact: Hashable, Identifiable {
let id: UUID
let name: String
let isPinned: Bool
}
final class ContactsViewController: UIViewController {
private var dataSource: UICollectionViewDiffableDataSource<Section, Contact.ID>!
private var contactsByID: [Contact.ID: Contact] = [:]
private func makeDataSource() {
let cellReg = UICollectionView.CellRegistration<UICollectionViewListCell, Contact.ID> {
[weak self] cell, _, id in
var config = cell.defaultContentConfiguration()
config.text = self?.contactsByID[id]?.name
cell.contentConfiguration = config
}
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
collectionView, indexPath, id in
collectionView.dequeueConfiguredReusableCell(using: cellReg, for: indexPath, item: id)
}
}
private func apply(_ contacts: [Contact], animated: Bool = true) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Contact.ID>()
snapshot.appendSections([.pinned, .all])
snapshot.appendItems(contacts.filter(\.isPinned).map(\.id), toSection: .pinned)
snapshot.appendItems(contacts.map(\.id), toSection: .all)
dataSource.apply(snapshot, animatingDifferences: animated) // diffs + animates for you
}
}
// Compositional layout — list appearance with a header
let layout = UICollectionViewCompositionalLayout { _, environment in
var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
config.headerMode = .supplementary
return .list(using: config, layoutEnvironment: environment)
}
Key Points:
- Section and item types must be
Hashable, and each item's hash must be stable and unique — prefer anIDover the whole model, otherwise editing a field reads as delete + insert apply(_:animatingDifferences:)is safe to call from a background queue as long as you're consistent; on iOS 15+ useawait dataSource.apply(snapshot)- Use
reconfigureItems(iOS 15+) instead ofreloadItemsto update a visible cell in place without recreating it - Pairs naturally with
UICollectionViewCompositionalLayoutandCellRegistration, which remove theregister/reuseIdentifierstring dance
Rate your understanding: