Ответ
Обычный 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:
- Декларативность: Вы описываете, как должны выглядеть данные, а не какие операции выполнить для обновления.
- Надежность: Исключаются классические ошибки
Invalid update. - Эффективность: Не нужно вручную вычислять минимальный набор изменений.
Когда использовать обычный 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)
Вот в чём, сука, сила-то:
- Декларативность. Ты не командуешь: «эй, удали третью строку!». Ты заявляешь: «брат, вот полный список того, что должно быть на экране». А система сама догадается, как к этому прийти.
- Надёжность. Классические ошибки с индексами уходят в пизду. Нельзя обновить то, чего нет.
- Эффективность. Не надо самому выдумывать велосипед с вычислением минимальных изменений. Всё уже придумано до нас.
А когда старый способ брать? Да когда таблица раз на хуй и не меняется, или когда тебе до такой степени надо выжать каждую миллисекунду, что даже diff-алгоритм кажется жирным. Но в 95% случаев — бери новую хуйню и не парься. В рот меня чих-пых, ведь правда удобно?