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 an ID over 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+ use await dataSource.apply(snapshot)
  • Use reconfigureItems (iOS 15+) instead of reloadItems to update a visible cell in place without recreating it
  • Pairs naturally with UICollectionViewCompositionalLayout and CellRegistration, which remove the register/reuseIdentifier string dance

Rate your understanding:

Ready to practice more UIKit?

Test yourself with our interactive quiz mode or browse all curated questions for this topic.