TableView ๋ฆฌํŒฉํ† ๋ง ํ•˜๋‹ค๊ฐ€ ๋ ˆ๊ฑฐ์‹œ๋ฅผ ๋‹ค๋ฅด๊ฒŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์„๊นŒํ•˜๋ฉด์„œ ์•Œ์•„๋ณด์•˜๋‹ค.

์ด๊ฑธ ๋ฐฐ์šฐ๋ฉด ์ด๋Ÿฐ๊ฒŒ ๊ฐ€๋Šฅํ•˜๋‹ค. ์ž๋™์œผ๋กœ ์ด๋ ‡๊ฒŒ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ฒ˜๋ฆฌํ•ด์ค€๋‹ค! iOS 13์—์„œ๋ถ€ํ„ฐ ์ ์šฉ๊ฐ€๋Šฅํ•˜๋‹ค.

Current State-of-the-Art

// MARK: UICollectionViewDataSource
 
func numberOfSections(in collectionView: UICollectionView) -> Int {
    return models.count
}
 
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return models[section].count
}
 
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for:indexPath) 
    // configure cell
    return cell
}   

์ด๊ฒŒ ์˜ˆ์ „์— ํ•˜๋˜ ๋ฐฉ์‹์ด๋‹ค. section ๊ฐœ์ˆ˜ ์ •ํ•ด์ฃผ๊ณ , section์•ˆ์˜ ๊ฐœ์ˆ˜๋ฅผ ์ •ํ•ด์ฃผ๊ณ , delegate๋กœ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ์ด ๋˜์—ˆ์„ ๋•Œ, ์–ด๋–ป๊ฒŒ cell์„ ๋งŒ๋“ค์–ด์ค„ ๊ฒƒ์ธ์ง€ ์ •์˜ํ•ด์ฃผ๋ฉด ๋˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

UICollectionViewDataSource

@MainActor protocol UICollectionViewDataSource

์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ํ† ๋Œ€๋Š” UICollectionViewDataSource๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด๋…€์„์€ protocol์ด๊ณ , viewController์—์„œ self.dataSource = self๋ฅผ ํ†ตํ•ด ์œ„์™€ ๊ฐ™์ด delegate๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•œ ์ด์œ ๋Š” viewController๊ฐ€ UICollectionViewDataSource๋ฅผ ์ฑ„ํƒํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

Apps Are Often Complicated

ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ 1์ฐจ์› ๋ฐฐ์—ด์—์„œ๋Š” ํŽธ๋ฆฌํ•˜์ง€๋งŒ, ๋ณต์žกํ•œ ๊ฒฝ์šฐ ์ด๋Ÿฌํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์€ ์ƒ๋‹นํžˆ ๋จธ๋ฆฌ์•„ํ”„๋‹ค.

  • Web service
  • Core Data
  • ๋’ค์˜ ViewController๋กœ ๋ถ€ํ„ฐ ์—…๋ฐ์ดํŠธ ๋˜๋Š” ๊ฒฝ์šฐ

์ •๋ง ๋‹จ์ˆœํ•˜์ง€๋งŒ, ๊ฐ€๋” ์šฐ๋ฆฌ๋Š” ์ด๋Ÿฐ ์—๋Ÿฌ๋ฅผ ๋งˆ์ฃผํ•˜๊ฒŒ ๋˜๋Š”๋ฐ..

StackOverflow์—์„œ ์ฐพ์•„๋ณด๊ณ  ๊ฒฐ๊ตญ, ์šฐ๋ฆฌ๋Š” reloadData๋ฅผ ์„ ํƒํ•œ๋‹ค. WWDC์—์„œ๋„ ์ „ํ˜€ ๋ฌด๋ฐฉํ•œ ํ–‰๋™์ด๋ผ๊ณ  ํ•˜๊ธดํ•˜์ง€๋งŒ, ์ด๋ ‡๊ฒŒ ํ• ๊ฒฝ์šฐ ์• ๋‹ˆ๋ฉ”์ด์…˜๋˜์ง€ ์•Š์€ ํšจ๊ณผ๊ฐ€ ๋‚˜ํƒ€๋‚œ๋‹ค.

What is the problem

์ด๋Ÿฌํ•œ ์ƒํ™ฉ์—์„œ ๋ฌธ์ œ๋Š” ์–ด๋””์— โ€œ์ง„์งœโ€๊ฐ€ ์žˆ๋Š๋ƒ์ด๋‹ค. ์ฆ‰, DataSource ์—ญํ• ์„ ํ•˜๋Š” DataController๊ฐ€ ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ๋ณ€ํ™”ํ•˜๋Š” ์ž์‹ ์˜ version, Truth๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. (own version of the truth) ๊ทธ๋ฆฌ๊ณ  UI ์—ญ์‹œ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋Š” truth๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ์ด ๋‘๊ฐœ๊ฐ€ ์„œ๋กœ ๋งž์ง€ ์•Š์„ ๊ฒฝ์šฐ ์œ„์˜ ์—๋Ÿฌ๊ฐ€ ๋ฟœ!ํ•˜๊ณ  ๋‚˜์˜จ๋‹ค.

๊ฒฐ๊ตญ, ์ด ๋ฌธ์ œ๋Š” ์ค‘์•™์ง‘์ค‘ํ˜•์œผ๋กœ ํ†ต์ œ๋˜๊ณ  ์žˆ๋Š” truth๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์ด ์›์ธ์ด๋‹ค.

New Approach

๊ทธ๋ž˜์„œ Apple์€ ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ์ ‘๊ทผ๋ฐฉ์‹์„ ๋„์ž…ํ•œ๋‹ค. ๊ทธ๊ฒƒ์ด Diffable DataSource์ด๋‹ค!

Diffable Data Source

performBatchUpdates()๊ฐ€ ์—†์–ด์ง„๋‹ค. (์—ฐ์‚ฐ์„ ํ†ตํ•ด animation ์ฃผ๋Š” method) ๊ท€์ฐฎ๊ณ , ๋ณต์žกํ•˜๊ณ  crash ์ค€๋‹ค. ์ด๋Ÿฐ ๊ฒŒ ํ•„์š”์—†๊ณ  ์ด์ œ apply ํ•œ๋ฐฉ์ด๋ฉด ์ž๋™์œผ๋กœ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•ด์„œ ์ ์šฉํ•œ๋‹ค.

Snapshots

์ด๊ฑธ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ƒˆ๋กœ์šด structure๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋Š”๋ฐ, ๊ทธ๋…€์„์€ snapshot์ด๋‹ค. ํ˜„์žฌ UI State์˜ truth๋ฅผ ๊ฐ€์ง„ ๋…€์„์ด๋‹ค.

  • Truth of UI State
  • Unique identifiers for sections and items
  • No more IndexPaths

์ด์ œ๋ถ€ํ„ฐ IndexPath๊ฐ€ ์•„๋‹ˆ๊ณ  identifier๋กœ ์ด๋ฅผ ๊ตฌ๋ถ„ํ•œ๋‹ค.

SnapShot์ด ์ ์šฉ๋˜๋Š” ์ง๊ด€์ ์ธ ๊ทธ๋ฆผ์€ ์œ„์™€ ๊ฐ™๋‹ค. ์ƒˆ๋กœ์šด Snapshot์œผ๋กœ ๊ธฐ์กด๊ฒƒ์„ ๋Œ€์ฑ„ํ•˜๋Š” ๊ฒƒ! ์ด ๋•Œ, Animation์€ system์ด ์ž๋™์œผ๋กœ ์ ์šฉํ•œ๋‹ค.

DiffableDataSource

  • UICollectionViewDiffableDataSource
  • UITableViewDiffableDataSource
  • NSCollectionViewDiffableDataSource (MacOS)
  • NSDiffableDataSourceSnapshot (Common)

์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•  ์ ์€, ์ด๋…€์„๋“ค์€ ๋”์ด์ƒ Protocol์ด ์•„๋‹ˆ๋ผ๋Š” ์ ์ด๋‹ค. class์ด๋ฉฐ, ์‚ฌ์šฉํ•  ์‹œ ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธํ•˜๊ณ  apply ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

Demo!

์•ž์œผ๋กœ ์„ค๋ช…ํ•  ์ฝ”๋“œ๋Š” Implementing Modern Collection Views์— ์žˆ๋‹ค.

๊ฒ€์ƒ‰์ฐฝ์— ์ž…๋ ฅํ•˜๋ฉด, ์ด์— ๋งž๋Š” ์‚ฐ์„ ํ•„ํ„ฐ๋งํ•ด์ฃผ๊ณ  ๋ณด์—ฌ์ฃผ๋Š” ๋™์ž‘์„ ํ•˜๋Š” ์•ฑ์ด๋‹ค. ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. Search bar์— text๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ์‹œ callback ํ•จ์ˆ˜๊ฐ€ ๋ถˆ๋ฆฐ๋‹ค.
  2. callback ํ•จ์ˆ˜๋‚ด์—์„œ๋Š” ํ•ด๋‹น ์ž…๋ ฅ์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์˜ค๋Š” queryํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
  3. ํ˜ธ์ถœํ•˜๋Š” query ํ•จ์ˆ˜๋‚ด์—์„œ๋Š” model layer์—์„œ ๊ฐ’์„ ๋ฐ›์•„์˜ค๊ณ , ์ƒˆ๋กœ์šด snapshot์„ ์ฐ๋Š”๋‹ค.(์ธ์Šคํ„ด์Šค ์ƒ์„ฑ)
  4. ์ฐ์€ snapshot์„ ํ˜„์žฌ diffableDataSource์— applyํ•œ๋‹ค.

Instructions

  1. Connect a diffable data source to your collection view.
  2. Implement a cell provider to configure your collection viewโ€™s cells.
  3. Generate the current state of the data.
  4. Display the data in the UI.

Connect a diffable data source to your collection view.

class MountainsViewController: UIViewController {
 
    var dataSource: UICollectionViewDiffableDataSource<Section, MountainsController.Mountain>!
}

๋จผ์ €, CollectionView์— dataSource๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

 
@MainActor class UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable

์ด ๋•Œ, ๋‘๊ฐœ์˜ Type์„ ๋ฐ›๋Š”๋ฐ ๋ชจ๋‘ Hashable์ด์–ด์•ผ ํ•œ๋‹ค.

SectionIdentifier Type

class MountainsViewController: UIViewController {
    enum Section: CaseIterable {
        case main
    }
    var dataSource: UICollectionViewDiffableDataSource<Section, MountainsController.Mountain>!
}

Apple ์—์„œ๋Š” Section์„ ๊ธฐ๋ณธ์ ์œผ๋กœ Enum์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. Enum์˜ ๊ฒฝ์šฐ์—๋Š” ์—ฐ๊ด€๊ฐ’์ด ์—†๊ฑฐ๋‚˜, ์—ฐ๊ด€๊ฐ’์ด ๋ชจ๋‘ Hashableํ•  ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ synthesize๋œ๋‹ค.

For an enum, all its associated values must conform to Hashable. (An enum without associated values has Hashable conformance even without the declaration.)

ItemIdentifier Type

ItemIdentifier ์—ญ์‹œ ๊ณ ์œ ํ•ด์•ผ ํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ Apple์—์„œ๋Š” UUID๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.

class MountainsViewController: UIViewController {
    enum Section: CaseIterable {
        case main
    }
    var dataSource: UICollectionViewDiffableDataSource<Section, MountainsController.Mountain>!
}
 
class MountainsController {
 
    struct Mountain: Hashable {
        let name: String
        let height: Int
        let identifier = UUID()
        func hash(into hasher: inout Hasher) {
            hasher.combine(identifier)
        }
        static func == (lhs: Mountain, rhs: Mountain) -> Bool {
            return lhs.identifier == rhs.identifier
        }
    }
}

Mountain์„ ๋ณด๋ฉด, Hashable์„ ์ฑ„ํƒํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ฐ instance๊ฐ€ ๋…๋ฆฝ์ ์ด๊ธฐ ์œ„ํ•ด UUID๋ฅผ ํ†ตํ•ด Equality๋ฅผ ์ฒ˜๋ฆฌํ•ด์ฃผ๊ณ  ์žˆ๋‹ค.

Connect CollceionView, Provider

์ด์ œ dataSource๋ฅผ ์‹ค์ œ ์ธ์Šคํ„ด์Šค๋กœ ๋งŒ๋“ค์–ด์„œ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค. ์ธ์ž๋กœ collectionView์™€ cellProvider๋ฅผ ๋ฐ›๋Š”๋‹ค.

@MainActor init(collectionView: UICollectionView, cellProvider: @escaping UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>.CellProvider)

collectionView์˜ ๊ฒฝ์šฐ ํ•ด๋‹น VC์—์„œ ์„ ์–ธํ•œ collcetionView๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ๋˜๊ณ , cell provider์˜ ๊ฒฝ์šฐ 3๊ฐœ์˜ ์ธ์ž(sectionIdentifier, indexPath, itemIdentifier)๋ฅผ ๊ฐ–๋Š” closure์ด๋‹ค.

dataSource = UICollectionViewDiffableDataSource<Section, MountainsController.Mountain>(collectionView: mountainsCollectionView) {
            (collectionView: UICollectionView, indexPath: IndexPath, identifier: MountainsController.Mountain) -> UICollectionViewCell? in
            // Return the cell.
        }

์—ฌ๊ธฐ์„œ closure ์ธ์ž๋Š” ์ด์ „์— DiffableDataSource๋ฅผ ์„ ์–ธํ–ˆ์„ ๋•Œ sectionIdentifier์™€ itemIdentifier๊ฐ€ ๋“ค์–ด์˜ค๊ฒŒ ๋œ๋‹ค.

Implement a cell provider to configure your collection viewโ€™s cells.

dataSource = UICollectionViewDiffableDataSource<Section, MountainsController.Mountain>(collectionView: mountainsCollectionView) {
            (collectionView: UICollectionView, indexPath: IndexPath, identifier: MountainsController.Mountain) -> UICollectionViewCell? in
            // Return the cell.
            guard let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier) else { return }
            cell.configure(mountain: identifier)
            return cell
        }

Apple ์ฝ”๋“œ์—์„œ๋Š” dequeueConfiguredReusableCell๋ฅผ ํ†ตํ•ด ์žฌํ™œ์šฉ๋œ cell์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋งˆ์ณค๋‹ค. ์ƒ์„ฑํ•ด์„œ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

Generate the current state of the data.

์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ์‹ค์ œ ๋งŒ๋“ค ์•ฑ์—์„œ interaction๊ณผ ์—ฐ๊ฒฐํ•ด์ค„ ์ฐจ๋ก€์ด๋‹ค. ์šฐ๋ฆฌ๋Š” ์‚ฌ์šฉ์ž text ์ž…๋ ฅ์— ๋”ฐ๋ผ ์ ์šฉํ•  ๊ฒƒ์ด๋‹ค.

// 01: Event ๋ฐœ์ƒ, 02: query ํ•จ์ˆ˜ ํ˜ธ์ถœ
extension MountainsViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        performQuery(with: searchText)
    }
}
 
// 03: ๋ชจ๋ธ๋กœ๋ถ€ํ„ฐ ๋ณ€๊ฒฝ๋œ ๊ฐ’์„ ๊ฐ€์ ธ์˜ด
extension MountainsViewController {
    func performQuery(with filter: String?) {
        let mountains = mountainsController.filteredMountains(with: filter).sorted { $0.name < $1.name } // model์—์„œ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ด
 
        var snapshot = NSDiffableDataSourceSnapshot<Section, MountainsController.Mountain>() // snapshot ์ƒ์„ฑ
        snapshot.appendSections([.main])
        snapshot.appendItems(mountains)
        dataSource.apply(snapshot, animatingDifferences: true) // 04: apply
    }
}

snapshot์„ ์ฐ์„ ๋‹น์‹œ์—๋Š” ๋น„์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์—ฌ๊ธฐ์— ์–ด๋–ป๊ฒŒ ๋ณด์—ฌ์งˆ ์ง€ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค. section์˜ ๊ฒฝ์šฐ ๋‹จ์ผ์ด๊ธฐ ๋•Œ๋ฌธ์— [.main]๋งŒ ๋„ฃ์–ด์ฃผ๊ณ , item์˜ ๊ฒฝ์šฐ ์›๋ž˜๋Š” identifier๊ฐ€ ๋“ค์–ด๊ฐ€์•ผ ํ•˜์ง€๋งŒ, Swift์˜ ๊ฒฝ์šฐ ๋ณด๋‹ค elegantํ•˜๊ฒŒ ์ž‘๋™ํ•˜๊ธฐ ์œ„ํ•ด own native type์„ ๋„ฃ์–ด๋„ ๋™์ž‘ํ•œ๋‹ค.

Display the data in the UI.

animation์„ true๋กœ ์ฃผ๋ฉด, ์œ„์™€๊ฐ™์ด ์˜ˆ์œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ฆ‰๊ฐ ์ ์šฉ๋œ๋‹ค. false์ธ ๊ฒฝ์šฐ ์˜ค๋ฅธ์ชฝ๊ณผ ๊ฐ™์ด ๋‚˜์˜จ๋‹ค.

์ถ”๊ฐ€

storyboard๋‚˜ xib๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งŒ๋“ค์–ด์ง„ ๋…€์„์˜ ๊ฒฝ์šฐ์—๋Š” register๊ฐ€ ํ•„์ˆ˜๋‹ค.

self.collectionView.register(DJCollectionViewCell.self, forCellWithReuseIdentifier: "cell")

๊ทธ๋ฆฌ๊ณ  ๋‚˜์„œ dequeue๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํ•ด์คฌ์—ˆ๋‹ค.

dataSource = UICollectionViewDiffableDataSource<Section, MountainsController.Mountain>(collectionView: mountainsCollectionView) {
            (collectionView: UICollectionView, indexPath: IndexPath, identifier: MountainsController.Mountain) -> UICollectionViewCell? in
            // Return the cell.
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? MountainCollectionViewCell else { preconditionFailure() }
            cell.configure(mountain: identifier)
            return cell
        }

๊ทธ๋Ÿฐ๋ฐ, ์ด์ž‘์—… ์—†์ด diffabledataSource๋ฅผ ๋งŒ๋“ค ๋•Œ, dequeueConfiguredReusableCell์„ ์‚ฌ์šฉํ•˜๋ฉด register์™€ ๋™์‹œ์— configuration๊นŒ์ง€ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ฐฉ์‹์ด Apple์—์„œ ์‚ฌ์šฉํ•œ ๋ฐฉ์‹์ด๋‹ค.

dataSource = UICollectionViewDiffableDataSource<Section, MountainsController.Mountain>(collectionView: mountainsCollectionView) {
            (collectionView: UICollectionView, indexPath: IndexPath, identifier: MountainsController.Mountain) -> UICollectionViewCell? in
            // Return the cell.
            guard let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier) else { return }
            cell.configure(mountain: identifier)
            return cell
        }

Considerations

Current Snapshot

// Empty snapshot
let snapshot = NSDiffableDataSourceSnapshot<Section, UUID>()
 
// Current data source snapshot copy
let snapshot = dataSource.snapshot()

ํ˜„์žฌ snapshot์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ณ , ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด copyํ•ด์„œ ๊ฐ–๋‹ค์ค€๋‹ค. ๊ทธ๋ž˜์„œ ์ด์ „ snapshot์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋Š”๋‹ค.

Snapshot State

// Snapshot State
var numberOfItems: Int { get }
var numberOfSections: Int { get }
var sectionIdentifiers: [SectionIdentifierType] { get }
var itemIdentifiers: [ItemIdentifierType] { get }

snapshot์˜ ๋‹ค์–‘ํ•œ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Configuring Snapshots

// Configuring Snapshots
func insertItems(_ identifiers: [ItemIdentifierType],
                 beforeItem beforeIdentifier: ItemIdentifierType)
func moveItem(_ identifier: ItemIdentifierType,
                 afterItem toIdentifier: ItemIdentifierType)
func appendItems(_ identifiers: [ItemIdentifierType],
                 toSection sectionIdentifier: SectionIdentifierType? = nil)
func appendSections(_ identifiers: [SectionIdentifierType])

snapshot์„ ๊ตฌ์ถ•ํ•˜๋Š”๋ฐ ์žˆ์–ด์„œ๋„ ๋‹ค์–‘ํ•œ method๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

Custom Identifiers

// Custom Identifiers
struct MyModel: Hashable {
    let identifier = UUID()
 
    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }
 
    static func == (lhs: MyModel, rhs: MyModel) -> Bool {
        return lhs.identifier == rhs.identifier
    }
}

Identifier๋Š” uniqueํ•˜๊ณ , hashable์„ ์ฑ„ํƒํ•ด์•ผ ํ•œ๋‹ค.

Get Identifier busing indexPath

// What About IndexPath-based APIs?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    if let identifier = dataSource.itemIdentifier(for: indexPath) {
    // Do something
    }
}

indexPath๋กœ๋ถ€ํ„ฐ identifier๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” API๋„ ์ œ๊ณตํ•œ๋‹ค. ํ•ด๋‹น API์˜ ์‹œ๊ฐ„๋ณต์žก๋„๋Š” Constant๋ผ๊ณ  ํ•œ๋‹ค.

Performance

๊ต‰์žฅํžˆ ๋น ๋ฅด๋‹ค๊ณ  ์ž๋ž‘ํ–ˆ๋‹ค. O(n)์ด๋ผ๊ณ  ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  apply() ํ˜ธ์ถœ์€ background queue์—์„œ Safeํ•˜๋‹ค๊ณ  ํ•œ๋‹ค!! ์•Œ์•„์„œ ํ•ด์ค€๋‹ค!!

Summary

  • Model์„ ๊ด€๋ฆฌํ•˜๋Š” controller์™€ ์‹ค์ œ UI์—์„œ ๋ณด์ด๋Š” Truth์˜ ์ฐจ์ด๋กœ DataSource๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์–ด๋ ต๋‹ค.
  • ์ด๋Ÿฌํ•œ ์ ์—์„œ Apple์€ Diffable Datasource๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.
  • Protocol ๋ฐฉ์‹์ด ์•„๋‹Œ Class ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ Snapshot์„ ์ฐ๊ณ  ์ด๋ฅผ apply ํ•จ์œผ๋กœ์จ ๋ณ€ํ™”๋ฅผ ํ†ตํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ค€๋‹ค.
  • Diffable DataSource ์ƒ์„ฑ, Cell Provider ์ •์˜, Snapshot ์ƒ์„ฑ, Diffable DataSource์— ์ ์šฉ์˜ 4๋‹จ๊ณ„๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • SectionIdentifier์™€ ItemIdentifier๋Š” Hashable์ด์–ด์•ผ ํ•˜๋ฉฐ, ์ด ๋•Œ UUID๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์†๋„๊ฐ€ ๊ต‰์žฅํžˆ ๋น ๋ฅด๋‹ค! ๊ทธ๋ฆฌ๊ณ  background queue์—์„œ apply()๋ฅผ ํ˜ธ์ถœํ•ด๋„ Safeํ•˜๋‹ค.
  • iOS 13์—์„œ๋ถ€ํ„ฐ ์ ์šฉ๊ฐ€๋Šฅํ•˜๋‹ค.

๋ง‰์ƒ ๋ณด๋‹ค๋ณด๋‹ˆ Hashable๊ฐ™์€ ํ”„๋กœํ† ์ฝœ์— ๋Œ€ํ•ด ์ •ํ™•ํžˆ ๋ชจ๋ฅด๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ด๋…€์„์„ ๋‹ค๋ค„๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค. ๋!

Reference