본문 바로가기
iOS/UIKit

DataSource말고 DiffableDataSource로

by 지금갑시다 2023. 1. 6.

 

DiffableDataSource

 

DiffableDataSource의 정의 먼저 봐보자(나는 UITableView를 예시로 삼아 글을 작성할꺼다!, UICollectionView도 거의 동일)

@preconcurrency @MainActor class UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject where SectionIdentifierType : Hashable, SectionIdentifierType : Sendable, ItemIdentifierType : Hashable, ItemIdentifierType : Sendable

 

 내 블로그에서 조금 다뤄봤던 @MainActor로 선언되어 있고, DataSource를 구성하는 값은 2개를 받는데, 

한개는 SectionIdentifier, 나머지 하나는 ItemIdentifier이다. where 뒤를 보면, 각각은 Hashable 프로토콜Sendable 프로토콜을 따라야한다고 나와있다.

 

 개인적으로 해석을 해보자면, 먼저 @MainActor로 선언되어 있으니, UI 변경에 관여를 하는 놈인 것 같고, SectionIdentifier, ItemIdentifier는 솔직히 아직은 잘 모르겠는데, TableView의 섹션에 관련된 놈과, 그 섹션에 있는 아이템에 관련된 놈들 이라고 생각이 된다.

 

@MainActor 와 Sendable을 보고 오면 조금 더 이해하는데 도움이 될 것 같다!

https://driveitlikeyoustoleitt.tistory.com/entry/MainActor-globalActor-조금-더-이해하기

https://driveitlikeyoustoleitt.tistory.com/entry/Sendable-프로토콜에-대해

 

자 그럼!! 일단 구현을 하면서 생각해보자!

우리의 구현 목표!

구현 목표

 

위를 구현하기 위해 DiffableData가 필요한데,

위의 정의에서 보았듯이, Hashable과 Sendable을 따르는 SectionIdentifier와 ItemIdentifier가 필요하다.

 

 SectionIdentifier는 Hashable을 따르는 enum을 활용해, 만들어주고 Item은 Hashable 프로토콜을 따르는 Fruit struct로 만들어 주자. 또한 enum과 struct는 Value 타입으로, 값이 전달될때 thread safe 하기에 Sendable도 준수하는 것이다.

enum Section {
    case first // 임의의 case값을 줌
}
    
struct Fruit: Hashable {
    let title: String  
}

위의 재료가 DiffableDataSource를 만드는 모든 재료

 

위를 이용해 dataSource를 만들어 보면, 

var dataSource: UITableViewDiffableDataSource<Section, Fruit>!

 

이제 해당 dataSource를 우리의 tableView에 등록해주는 과정을 해줍니다.

dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, itemIdentifier in
      let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
      cell.textLabel?.text = itemIdentifier.title
      return cell
})

 원래 TableViewCell을 deque해주는 함수를 사용해 UITableViewCell을 return 하는 함수와 유사하기에 이해가 잘 간다.

조금 다른 부분이라면, dataSource를 사용할 tableView를 채택해주는 과정이 있다는 것이다.

 

 이제 cell을 화면에 표현해주는 부분은 끝났고, cell이 삭제되거나, 추가될때 dataSource가 바뀌어야 하기에 이를 바꿔주는 snapshot이 필요하다.

 

 snapshot이란, 이름 그대로 그 순간의 데이터를 사진으로 찍은 것이다. 그 순간의 데이터 snapshot을 찍어 dataSource에 적용해주면, dataSource가 바뀌고, 이는 위에서 보았듯이, Main thread에서 진행되는 놈이기에 화면변경에 영향을 미쳐 표시되는 data들도 함께 변하는 것이다! (근데 사실은 앞의 문장은 결과론적인 해석이고, 실제로는 원래 DiffableDataSource를 변화하는 데이터에 맞게 화면도 변화시키려고 애초에 구현을 해놓아서 Main thread에서 진행이 되는 것이다.)

 

그럼 이제 snapshot을 만들어서 dataSource에 적용시켜보자!

// didTapAdd() 액션 발생!

var fruits: [Fruit] = []

@objc func didTapAdd() {
        let actionSheet = UIAlertController(title: "Select Fruit", message: nil, preferredStyle: .actionSheet)
        
        for x in 0...100 {
            actionSheet.addAction(UIAlertAction(title: "Fruits\(x+1)", style: .default, handler: { [weak self] _ in
                let fruit = Fruit(title: "\(x+1)")
                self?.fruits.append(fruit)
                self?.updateDatasource(num: x)
            }))
        }
        present(actionSheet, animated: true)
}


func updateDatasource() {
    var snapshot = NSDiffableDataSourceSnapshot<Section, Fruit>()
    snapshot.appendSections([.first])
    snapshot.appendItems(fruits, toSection: .first)
    dataSource.apply(snapshot, animatingDifferences: true)
}

 updateDatasource()라는 액션이 발생하면, 그 순간 snapshot을 만들어, Section.first 섹션에 들어갈 item인 fruits를 넣어

dataSource에 apply() 해주면 완성이다!

 

 apply()의 파라미터인 animatingDifference를 보면, 이는 이전의 dataSource와 다르게 dataSource가 변했을때, animation을 주어 자연스럽게 tableview가 변할 수 있게 도와주고, 정말 DiffableData를 쓰는 아주 강력한 이유중에 하나이다.

 

위처럼 만들게 되면, 우리의 구현목표 달성!

 

**DiffableDataSource 구현 방법 정리**

1. SectionIdentifier, ItemIdentifier를 만들어 주고

2. DiffableDataSource를 만들어 주고

3. DiffableDataSource로 tableView를 채택해주고, cell을 설정해주며

4. 데이터의 변경이 있을때, snapshot을 만들어 만들어준 DiffableDataSource에 apply해주면 끝!

 

 

 

그럼 이제 DiffableDataSource는 기존에 우리가 사용하던 DataSource와는 어떤 점이 다른 것인가?

 

1. 기존에는 UITableViewDataSource의 프로토콜을 채택해서, 프로토콜의 필수 함수들을 구현해주어야 했지만, 이제는 그렇게 할 필요가 없다.

2. 원래는 데이터가 변화했을때, tableView.reloadData()를 해주어야 했지만, 지금은 dataSource.apply()로 UI의 변화를 시킨다.

3. 위의 2번 덕분에 reloadData()를 사용하지 않아서, snapshot이 변화할 때 애플이 제공해주는 자연스러운 animation을 사용할 수 있다. reload의 경우 화면이 매끄럽지 못하게 업데이트가 된다.

 

 

 위의 장점들을 미루어 보아! DiffableDataSource는 TableView 혹은 CollectionView에 있는 데이터들의 변화가 자주 생기는 경우에 사용하면 좋을 것 같다!

 

끗!

 

REF: https://www.youtube.com/watch?v=Q2SmtfaxuW8 

 

'iOS > UIKit' 카테고리의 다른 글

delegate가 weak, optional타입을 가지는 이유  (0) 2022.08.30