В чем разница между обычным Data Source и Diffable Data Source в UIKit?

Ответ

Обычный Data Source (протоколы UITableViewDataSource/UICollectionViewDataSource) требует ручного управления состоянием и обновлениями. Diffable Data Source (классы UITableViewDiffableDataSource/UICollectionViewDiffableDataSource) автоматически вычисляет и анимирует изменения между состояниями данных.

Сравнительная таблица:

Характеристика Обычный Data Source Diffable Data Source
Управление состоянием Вы храните массив данных и вручную синхронизируете его с ячейками. Вы работаете со снимками (Snapshot), которые описывают полное состояние данных.
Обновление UI reloadData() (без анимации) или ручные insert/delete/move вызовы внутри performBatchUpdates. Один вызов apply(_:animatingDifferences:). Система сама вычисляет diff и анимирует изменения.
Идентификация По IndexPath (секция, ряд). По уникальным идентификаторам, соответствующим Hashable.
Безопасность Высокий риск ошибок (несоответствие числа элементов, некорректные IndexPath). Безопасен по дизайну. Нельзя сослаться на несуществующий элемент.
Производительность reloadData() перезагружает все. Ручные обновления эффективны, но сложны в реализации. Diff-алгоритм оптимизирован. Отлично подходит для частых и сложных обновлений.

Пример Diffable Data Source:

// 1. Определите типы для секций и элементов (должны быть Hashable).
enum Section: CaseIterable { case main }
struct Item: Hashable {
    let id: UUID
    let title: String
}

// 2. Создайте источник данных.
var dataSource: UICollectionViewDiffableDataSource<Section, Item>!

dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) {
    (collectionView, indexPath, item) -> UICollectionViewCell? in
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MyCell
    cell.configure(with: item)
    return cell
}

// 3. Создайте и примените снимок (Snapshot).
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems([item1, item2, item3]) // Массив элементов

// 4. Примените снимок. Изменения будут автоматически анимированы.
dataSource.apply(snapshot, animatingDifferences: true)

// 5. Для обновления просто создайте новый снимок с актуальным состоянием и примените его.
var newSnapshot = NSDiffableDataSourceSnapshot<Section, Item>()
newSnapshot.appendSections([.main])
newSnapshot.appendItems([updatedItem1, item2, newItem4]) // Удалили item3, добавили newItem4
dataSource.apply(newSnapshot, animatingDifferences: true) // Анимация удаления и добавления

Ключевые преимущества Diffable Data Source:

  1. Декларативность: Вы описываете, как должны выглядеть данные, а не какие операции выполнить для обновления.
  2. Надежность: Исключаются классические ошибки Invalid update.
  3. Эффективность: Не нужно вручную вычислять минимальный набор изменений.

Когда использовать обычный Data Source: В очень простых статических таблицах или при жестких требованиях к производительности, где diff-алгоритм может быть излишним.

Ответ 18+ 🔞

А, слушай, смотри, вот эта вся хуйня с таблицами и коллекциями. Раньше-то, блядь, как было? Обычный DataSource, ёпта. Сидишь, как дурак, держишь в голове массив, а потом пытаешься его вручную синхронизировать с тем, что на экране. Один неверный чих — и тебе в лоб: Invalid update: invalid number of rows... Пиздец, а не жизнь! Волнение ебать, терпения ноль ебать.

А теперь, сука, придумали Diffable Data Source. Это ж, блядь, как будто тебе дали волшебную палочку вместо лома. Ты не говоришь таблице: «вот тут удали, тут вставь, а это передвинь». Ты просто показываешь ей два фото: «вот как было, а вот как должно стать». А она сама, хитрая жопа, всё посчитает и красиво анимирует. Ебушки-воробушки!

Вот, глянь, как они друг другу противостоят, эти два зверя:

Прикол Обычный Data Source Diffable Data Source
Кто за состояние отвечает? Ты, мудак. Сам хранишь массив и сам за него отвечаешь. Ты работаешь со снимками (Snapshot), типа «вот полная картина мира на сейчас».
Как обновить? Или reloadData() (всё моргнёт без анимации), или танцы с бубном в performBatchUpdates. Один неверный индекс — и привет, краш. Один вызов apply(). Всё. Система сама разберётся, что добавилось, что ушло, и красиво это покажет.
Как ячейки искать? По IndexPath (секция, ряд). Легко ошибиться и ткнуть в пустоту. По уникальным ID, которые должны быть Hashable. Нельзя ткнуть в то, чего нет — система не даст.
Надёжность Нулевая, если ты не бог. Почти абсолютная. Дизайн такой, что накосячить сложно.
Скорость reloadData() грузит всё. Ручные обновления быстрые, но, бля, их же ещё правильно написать. Diff-алгоритм быстрый, оптимизированный. Для сложных и частых обновлений — просто овердохуища.

А вот, смотри, как эту новую хуйню в коде используют. Блядь, красиво же!

// 1. Говоришь, какие у тебя секции и элементы будут. Главное — чтобы они были Hashable, а то нихуя не заработает.
enum Section: CaseIterable { case main }
struct Item: Hashable {
    let id: UUID // Вот это — твой уникальный ключ, без него никуда.
    let title: String
}

// 2. Создаёшь сам источник данных.
var dataSource: UICollectionViewDiffableDataSource<Section, Item>!

dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) {
    (collectionView, indexPath, item) -> UICollectionViewCell? in
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MyCell
    cell.configure(with: item) // Настраиваешь ячейку под конкретный item.
    return cell
}

// 3. Делаешь «фотку» желаемого состояния.
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main]) // Говоришь, какие секции будут.
snapshot.appendItems([item1, item2, item3]) // Списываешь в них элементы.

// 4. Показываешь фотку таблице. Она сама всё отрисует, да ещё и с анимацией!
dataSource.apply(snapshot, animatingDifferences: true)

// 5. Захотел обновить — делаешь НОВУЮ фотку и показываешь её.
var newSnapshot = NSDiffableDataSourceSnapshot<Section, Item>()
newSnapshot.appendSections([.main])
// Допустим, item3 удалился, а newItem4 появился.
newSnapshot.appendItems([updatedItem1, item2, newItem4])
// Применяешь. Система сама поймёт, что item3 исчез, а newItem4 появился, и анимирует это.
dataSource.apply(newSnapshot, animatingDifferences: true)

Вот в чём, сука, сила-то:

  1. Декларативность. Ты не командуешь: «эй, удали третью строку!». Ты заявляешь: «брат, вот полный список того, что должно быть на экране». А система сама догадается, как к этому прийти.
  2. Надёжность. Классические ошибки с индексами уходят в пизду. Нельзя обновить то, чего нет.
  3. Эффективность. Не надо самому выдумывать велосипед с вычислением минимальных изменений. Всё уже придумано до нас.

А когда старый способ брать? Да когда таблица раз на хуй и не меняется, или когда тебе до такой степени надо выжать каждую миллисекунду, что даже diff-алгоритм кажется жирным. Но в 95% случаев — бери новую хуйню и не парься. В рот меня чих-пых, ведь правда удобно?