UIkit

Diffable DataSource

2료일 2025. 3. 9. 20:29
와… 진짜 오랜만에 UIKit 관련 공부를 진행하는  같네요. 유킷으로 개발한 지가  됐지만, 그래도 이건 알아야 하는 중요한 지식이라 정리 시작해볼게요! 오늘은 Diffable DataSource 대해 자세히 다뤄보고,  내용을 바탕으로 블로그 글을 완성해보겠습니다.

등장배경

// 기존 방식
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return items.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    cell.textLabel?.text = items[indexPath.row]
    return cell
}

// 데이터 업데이트 시
items = newItems
tableView.reloadData() // 전체 테이블 다시 로드

기존에는 테이블이나 콜렉션 내부의 데이터를 사용하거나 변경할때 indexPath를 통해 Cell 을 가져오고 데이터를 변화시켜 주었다. 또한 데이터 업데이트로 다시 뷰를 그릴때는 reloadData() or performBatchUpdates()를 호출해주었다.

하지만 전체 셀을 새로 그리므로 애니메이션 없이 깜빡이는 현상이 발생하고 불필요한 렌더링으로 리소스를 많이 소모하고 데이터 변화가 적더라도 전체를 다시 불러오기 때문에 효율적이지 않았습니다. 또한 비동기 작업으로 데이터를 로딩할때 시점이 꼬이는 문제가 발생하였습니다

그래서 Diffable DataSource가 나왔습니다

DiffableDataSource란?

1. 식별자 기반 데이터관리: 각 항목은 고유 식별자를 가져 데이터변경시 어떤 항목이 추가, 삭제, 또는 수정되었는지 명확히 추적할 수 있다.

2. 스냅샷 기반 업데이트: 현재 상태와 새로운 상태를 스냅샷으로 비교해 차이점만 업데이트합니다.

3. 쓰레드 안정성: 백그라운드 쓰레드에서 데이터 처리하고 메인쓰레드에서 UI 업데이트

쉽게 말해, Diffable DataSource는 데이터 타입을 정의하고 스냅샷을 만들어 뷰에 반영하는 방식이에요. 데이터가 바뀌면 새로운 스냅샷을 생성해서 적용하고, 이전 스냅샷과 비교해 달라진 부분만 부드럽게 업데이트해줍니다. 특히 애니메이션을 자동으로 지원해서 UI 전환이 자연스러워요. 이제 indexPath를 직접 다룰 필요 없이 고유 식별자를 통해 데이터를 배치할 수 있게 됐습니다.

작동 과정

1. DiffableDataSource 생성하고, ViewController와 연결

enum Section: Int, CaseIterable, Hashable {
        case pending
        case completed
    }
    
    struct TodoItem: Hashable {
        let id = UUID()
        var title: String
        var isCompleted: Bool
        
        func hash(into hasher: inout Hasher) {
            hasher.combine(id)
        }
    }
private func configureDataSource() {
        // 1. 셀 등록
        let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, TodoItem> { cell, indexPath, item in
            // 2. 셀 콘텐츠 설정
            var content = cell.defaultContentConfiguration()
            content.text = item.title
 			dataSource = UICollectionViewDiffableDataSource<Section, TodoItem>(collectionView: collectionView) {
            (collectionView, indexPath, item) -> UICollectionViewCell? in
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
        }

이것도 iOS14에 나온 개념이라 정리를 하자면.... collectionView에서만! 테이블뷰에는 없나보다.. 아마 단일 셀이라 필요가 복잡성이 없기에 그럴수도?

CellRegistration을 통해 커스텀 셀을 컬렉션 뷰에 등록할 수 있다.

SupplementaryRegistration을 통해 커스텀 뷰를 컬렉션 뷰에 등록할 수 있다.

2. 초기 스냅샷을 만들고 DiffableDataSource에 적용

NSDiffableDataSourceSnapshot: 특정 시점의 뷰에서 데이터 상태를 표현.

preconcurrency
struct NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> where SectionIdentifierType : Hashable, SectionIdentifierType : Sendable, ItemIdentifierType : Hashable, ItemIdentifierType : Sendable

스냅샷을 사용하여 뷰에 표시되는 데이터의 초기 상태 설정하고 스냅샷을 사용하여 뷰에 표시되는 데이터에 대한 변경 사항을 반영합니다.

Section,Item 들은 고유한 identifier로 구별해야하기에 Hashable프로토콜을 채택해야한다. 

 private func updateUI() {
        // 1. 스냅샷 생성
        var snapshot = NSDiffableDataSourceSnapshot<Section, TodoItem>()
        

        let pendingItems = todos.filter { !$0.isCompleted }
        let completedItems = todos.filter { $0.isCompleted }
        
        snapshot.appendItems(pendingItems, toSection: .pending)
        snapshot.appendItems(completedItems, toSection: .completed)
        
        // 4. 스냅샷 적용
        dataSource.apply(snapshot, animatingDifferences: true)
    }

애플 공식문서 코드이다. 빈 스냅샷을 생성하고 세개의 항목이 있는 단일섹션으로 채웠다. 그후 apply하여 이전 상태와 새 상태 사이의 ui업데이트를 애니메이션화했다.

2. CellRegistration & 데이터 소스 정의

 

 

 

섹션 헤더/ 푸터 추가

// 헤더 등록 (iOS 14+)
let headerRegistration = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView.elementKindSectionHeader) { 
    (headerView, elementKind, indexPath) in
    
    var configuration = headerView.defaultContentConfiguration()
    configuration.text = "섹션 헤더"
    headerView.contentConfiguration = configuration
}

// 데이터 소스에 supplementaryViewProvider 설정
dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
    if kind == UICollectionView.elementKindSectionHeader {
        return collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: indexPath)
    }
    return nil
}

 

결국 Diffable Datasource는 복잡한 UI상태 관리나 데이터 변경이 자주 발생할때 사용하면 좋을 것 같다.

고유 식별자를 활용한 선언적 방식으로 UI업데이트 하고 자동애니메이션이 되고 가독성 향상 및 버그를 예방할 수 있다.

 

참고자료

https://applecider2020.tistory.com/37

 

[CollectionView] Diffable DataSource 이해하기 (1/3) - Advances in UI Data Sources (WWDC19)

CollectionView / TableView와 관련해서 "Diffable DataSource" 개념이 등장했다. 러닝커브가 조금 있는 내용이라 포스팅을 남기려고 한다. ✏️ 새로운 기술을 습득하기 가장 좋은 방법은 Apple이 만든 WWDC 영

applecider2020.tistory.com