tableView에 모델을 바인딩하면서 발생한 문제점을 발견했다.
모델이 변경되었을 때 테이블뷰에 대한 애니메이션 처리를 핸들링할 수 없었고
tableView 전체를 리로드 했다.
검색을 통해서 performbatchupdates()로 구현할 수 있다는 걸 알았지만,
혹시나 하는 마음에 WWDC에 tableView를 검색했다.
2019년 영상에서 tableView, collectionView의 애니메이션을 쉽게 구현할 수 있는
새로운 친구를 확인했다.
오늘 소개할 친구는 UITableVIewDiffableDataSource다.
기존에 사용했던 DataSource는 Controller에서 데이터를 변경하고 UI에게 변경했다는 걸 알렸다.
잘못 업데이트할 경우에 에러가 발생한다. 특히 IndexPath를 잘못 사용했을 때 빈번하게 앱 크러쉬가 발생한다.
DiffableDataSource는 performBatchUpdates()를 사용하지 않기 때문에 복잡하지 않고,
apply()를 사용해서 구현하면 된다.
snapshot은 현재 UI의 상태를 나타낸다.
(더 이상의 IndexPaths는 없다고 했지만, 배열 i번째 모델이 바뀔 경우에는 indexPath를 사용해야 할 거 같다.)
여기까지가 전반적인 UITableViewDiffableDataSource를 사용하는데 필요한 개념이다.
실제로 어떻게 구현하는지 살펴보겠다.
UITableViewDiffableDataSource은 2가지 제네릭 타입을 갖고 있다.
Section은 TableView의 SectionSectionIdentifierType을 말하며 ItemIdentifierType은 셀 하나에 매핑될 모델을 뜻한다.
두 개의 제네릭 모두다 Hashable 프로토콜을 채택하고 있는데 그 이유는 뒤에서 나올 스냅샷을 생성하고 비교할 때
모델이 변경되었는지 파악하기 위해서이다.
class UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject
where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
초기화 시킬 때 tableView를 넣어주고, 후행 클로저에서는
func tableView(_ tableView:, cellForRowAt:) 에서 구현해줬던 부분을 구현한다.
lazy var viewModel: UITableViewDiffableDataSource<Section, Item> = .init(tableView: tableView) { tableView, indexPAth, item in
// configure cell..
return cell
}
model이 변경될 때마다 아래 메소드를 호출하면
이전 snapshot과 변경된 snapshot을 비교해서 tableView 애니메이션을 처리해준다.
func applySnapshot(_ animatingDifferences: Bool = true) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections(section)
snapshot.appendItems(item)
viewModel.apply(snapshot, animatingDifferences: animatingDifferences)
}
마지막으로 제네릭이 Hashable을 채택한 이유는 snapshot을 비교할 때 hashValue를 사용하는데
어떤 값들을 조합해서 hash할건지 프로그래머가 지정해줄 수 있다.
struct Developer: Hashable {
var id: UUID = .init()
var name: String
var age: Int
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(name)
hasher.combine(age)
}
}
가장 중요한 점!!!
applySnapshot()은 background queue에서 호출하는 게 좋다. (모델이 많아질 경우를 대비해서)
API가 똑똑하기 때문에 UI를 변경하는 로직은 main queue에서 동작하도록 구현이 되어있다.
사용할 때 main 과 background 둘 다 상관없지만 한 곳에서만 호출하라고 언급했다.
기존에 사용했던 UITableViewDataSource 메소드를 사용하기 위해서는 UITableViewDiffableDataSource를 상속받는 클래스를 만들고
오버라이드 하는 방식으로 사용할 수 있다.
IndexPath를 사용하지 않고
viewModel.itemIdentifier(for: indexPath)를 사용하면
해당 열에 할당된 모델을 사용할 수 있다.
performBatchUpdates를 사용하지 않고도
apply()만 호출하면 테이블 뷰와 관련된 애니메이션을 처리해준다.
iOS 13 부터 적용가능하기 떄문에 이전 버전에서는 사용할 수 없다.
Github: github.com/GangWoon/DiffableDataSource