UIKit

DiffableDataSource

Phililip
728x90

이번 글은 DiffableDataSource에 대해 알아보겠습니다.

 

 

 

## 1. Data Source 프로토콜? 🤔

 

DiffableDataSource는 뒤에 Data sources라는 말이 붙었듯이, collection View와 tableView에서 사용하는 클래스에요.

 

 

우선 DiffableDataSource를 쉽게 이해하기 위해선 기존에 UI가 어떤 방식으로 업데이트되었는지 알아야 해요.

 

기존 Data Source 프로토콜 방식이 어땠는지 살펴볼게요.

 

 

출처 : https://developer.apple.com/videos/play/wwdc2019/220/

 

UI가 업데이트되어야 할 경우에는 UI가 Controller에게 

 

- section의 개수는 몇 개야?

- section 안에 cell의 개수는 몇 개야?

- cell은 어떻게 그려야 해?

- ...

 

을 물어보게 됩니다.

 

 

 

만약에 서버 통신을 해야 한다면

 

출처 : https://developer.apple.com/videos/play/wwdc2019/220/

 

 

Controller가 서버 통신 이후에 UI에게 값이 바뀌었으니 UI를 다시 그려줘! 라고 요청하게 되죠.

 

 

 

 

이렇게 이상적으로만 동작한다면 좋겠지만...

 

복잡한 동작으로 인해 업데이트가 잘못된다면... 크래쉬가 발생할 수 있습니다.

(Data 업데이트 전에 UI가 먼저 업데이트된다던지... 등)

 

출처 : https://developer.apple.com/videos/play/wwdc2019/220/

 

 

저흰 이런 문제를 만들지 않기 위하여, data source 관리를 철저하게 해줘야 합니다.

 

왜냐하면, data source와 UI 상태가 항상 일치해야 하기 때문이죠.

 

 

 

 

 

여기서 Apple 개발자들은 의문점을 느끼게 됩니다.

 

까딱 잘못 쓰면 에러 나고 크래쉬 나는데... 이렇게 힘들게 해야 함...? 🤔 🤔 🤔

 

 

그래서 WWDC 2019에서 처음으로 DiffableDataSource가 나오게 됩니다.

 

 

 

 

## 2. DiffableDataSource 

 

 

DiffableDataSource는 performBatchUpdates를 사용하지 않습니다. 대신에 apply 메서드를 사용해요.

 

 

출처 : https://developer.apple.com/videos/play/wwdc2019/220/

 

 

 

그리고 SnapShot이란 데이터 타입이 추가되었어요.

 

 

출처 : https://developer.apple.com/videos/play/wwdc2019/220/

 

 

현재 UI 상태를 나타내고, section과 item(cell)들은 각각 고유한 ID를 가지며, IndexPaths를 사용하지 않습니다.

 

그래서 기존에 indexPath를 이용해서 UI를 업데이트하는 방식이 아닌, 고유한 ID를 가지고 UI를 업데이트시켜줍니다.

 

 

 

예시를 들어서 볼까요??

 

 

출처 : https://developer.apple.com/videos/play/wwdc2019/220/

 

 

FOO, BAR, BIF를 출력한 UI가 있다고 가정해볼게요. 이게 지금 현재 SnapShot이 되겠죠.

 

그리고 UI를 업데이트해야 하는 상황이 온다면,

 

출처 : https://developer.apple.com/videos/play/wwdc2019/220/

 

 

새로운 Snapshot을 만들고 apply()를 호출하는 거예요.

 

 

apply를 하게 되면, 현재 snapshot과 바뀌어야 할 snapshot을 알고 있으니 적절한 애니메이션과 함께 최종 snapshot을 만드는 것이죠.

 

즉, UI가 뭐가 변경되었고 뭐가 삭제되었고 애니메이션을 어떻게 줄지 Snapshot이 알아서 다 관리해주는 거예요.

 

 

출처 : https://developer.apple.com/videos/play/wwdc2019/220/

 

 

 

 

추상적인 얘기는 여기까지 하고... 이제부턴 코드로 하나씩 살펴볼게요.

 

 

 

## 3. 실습

 

 

실습으로 아래처럼 만들어볼게요.

 

 

 

 

전체 프로젝트는 github에 올려두었으니 참고해주세요.

 

 

 

우선 UICollectionViewDiffableDataSource부터 살펴볼게요.

 

 

 

UICollectionViewDiffableDataSource를 생성할 때, SectionIdentifierType과 ItemIdentifierType을 등록해줘야 합니다.

 

그리고 Section과 Item은 모두 Hashable을 준수해야 해요.

위에서 UI 업데이트를 할 때 고유한 ID를 가지고 업데이트한다고 했으니, 어찌 보면 당연한....

 

 

 

Hashable을 준수하도록 Section과 Item에 해당하는 Person 클래스를 만들어줄게요.

 

enum Section: CaseIterable {
    case main
}

 

struct Person: Hashable {
    let name: String
    let identifier = UUID()

    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }

    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.identifier == rhs.identifier
    }
}

 

 

엥... 근데 Section은 Hashable을 준수하지 않는데요???라고 말할 수 있지만

 

enum의 경우, 모든 case와 associated value가 hashable 한 경우에는 자동으로 Hashable을 준수하게 됩니다.

 

 

 

 

 

그다음엔, CollectionView에 추가할 Cell을 만들어줍시다.

 

class LabelCell: UICollectionViewCell {
    static let reuseIdentifier = "label-cell-reuse-identifier"
    let label = UILabel()
    // ...
}

 

 

 

저는 스토리보드를 사용하지 않았기 때문에 CollectionView에 Cell을 등록(register)해줘야 해요. 이번에는 CellRegistration 구조체를 사용해서 진행해볼게요.

 

init(handler: @escaping UICollectionView.CellRegistration<Cell, Item>.Handler)

 

Cell 타입과 Item 타입은 제네릭 파라미터로 되어있고, handler를 통해서 cell을 어떻게 출력할지 설정해줄 수 있습니다.

 

 

 

 

제 경우에는 Cell = LabelCell이 될 것이고, Item은 Person이 되겠죠??

 

그리고 handler를 통해서 cell label에 값을 설정해줘 볼게요.

 

let cellRegistration = UICollectionView.CellRegistration<LabelCell, PeopleController.Person> { (cell, indexPath, person) in
    cell.label.text = person.name
}

 

위에서 만든 cellRegistration은 DiffableDataSource를 collectionView에 연결시킬 때 사용됩니다.

(cellRegistration에는 cell을 어떻게 출력해줄지에 대한 정보가 저장되어 있는 거예요.)

 

 

 

그다음엔, DiffableDataSource를 CollectionView에 연결시켜줄게요.

 

dataSource = UICollectionViewDiffableDataSource<Section, PeopleController.Person>(collectionView: peopleCollectionView, cellProvider: { (collectionView, indexPath, identifier) -> UICollectionViewCell? in
    return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
})

 

이게 어떤 말이냐...

 

- DiffableDataSource 객체를 만들건데,

- 섹션은 Section 이란 타입을 쓰고, Item은 Person이란 타입을 쓸 거야

- 이렇게 만든 DiffableDataSource를 peopleCollectionView에 연결시키고,

- cellProvider를 보면 Cell을 어떻게 그릴지 알 수 있어.

- Cell은 기존에 생성한 cellRegistration 정보를 토대로 cell을 구성하고 dequeueConfiguredReusableCell을 통해서 cell을 재사용할 수 있도록 할 거야.

 

라고 이해를 하면 좋을 것 같아요.

 

제일 중요한 것은 위 코드를 통해서 CollectionView와 DataSource가 연결되었다는 거예요.

 

 

 

 

이제 마지막으로, DataSource를 연결시켜줬으면 Snapshot을 만들고 Apply 해줘야 합니다.

 

 

아래 코드는 searchBar에서 text가 변경될 때 호출되는 method인데요,

 

func performQuery(with filter: String?) {
    let people = peopleController.filteredPeople(with: filter).sorted { $0.name < $1.name }
    var snapshot = NSDiffableDataSourceSnapshot<Section, PeopleController.Person>()
    snapshot.appendSections([.main])
    snapshot.appendItems(people)
    dataSource.apply(snapshot, animatingDifferences: true)
}

 

우선 NSDiffableDataSourceSnapshot을 사용해서 빈 snapshot을 만들어주고,

 

 snapshot 안에 출력해야 할 section과 아이템을 넣어준 뒤,

 

dataSource한테 변경될 snapshot을 넘겨주고 apply를 해주면 끝입니다.

 

 

 

 

 

## 4. 참고

 

 

 


 

이번 글은 여기서 마무리.

반응형