본문 바로가기

iOS

[WWDC 19] 디퍼블 데이터 소스

기존의 데이터소스가 아닌 새로운 데이터 소스, 디퍼블 데이터 소스에 대해서 알아보자!

여기 나온 내용은 WWDC19 Advance in UI Data Sources에서 가져왔습니다.

Advances in UI Data Sources - WWDC19 - Videos - Apple Developer

 

Advances in UI Data Sources - WWDC19 - Videos - Apple Developer

Use UI Data Sources to simplify updating your table view and collection view items using automatic diffing. High fidelity, quality...

developer.apple.com


목차

1. Current state-of-the-art

2. A new approach

3. Demos 중 한개만

4. Considerations


Current state-of-the-art

지금은 collectionView에 데이터를 넘겨줄 때 UICollectionViewDataSource 프로토콜을 ViewController가 채택을 하여 필수적으로 section 안 아이템 개수랑 cell 정보 이 2가지를 UI쪽에 넘겨주어야 합니다. 또 section의 개수도 넘겨줄 수 있습니다. 여러개의 section과 여러개의 item을 쓸 때는 간단하게 이차배열을 이용해서 데이터를 configure해줍니다.

 

그런데 데이터 구조는 점점 더 이차배열보다 훨씬 복잡해지고 controller도 core data와 web services 등 다양하게 상호작용을 하다보니 점점 복잡해지면서 문제가 발생합니다.

 

 

Controller가 Web Service 요청을 받고 있다고 가정해봅시다.

UI Data Source를 지원하고 있던 Controller는 웹 서비스 요청을 받게 되고 이 요청에 대한 정보를 업데이트하기 위해 UI 업데이트를 해줍니다. 보통 performBacthUpdate나 reloadData 메소드를 사용합니다.

 

하지만 performBacthUpdate 메소드를 이용하면 오류가 쉽게 날뿐만 아니라 굉장히 복잡하고, reloadData 메소드를 이용하면 애니메이션 효과가 없어서 사용자 경험을 저하시킨다고 합니다.

 

참고) WWDC에서 perfomBatchUpdate방식은 완벽한 방식은 아니고, reloadData방식은 적절하다고 합니다.

And eventually, you might get frustrated and just call reloadData. And we talked about this last year, and that's fine. That's correct. Your app looks okay.

 

여기서 가장 큰 문제점은 데이터가 바뀌면 UI업데이트를 어떻게 해야 할까입니다.

떱떱에서는 아래와 같이 설명해줍니다. controller는 자신이 가지고 있는 데이터 버전에 대해 truth가 있고 UI도 자기 버전에 대한 truth가 있습니다. 이 두개가 서로 맞지 않으면 에러가 난다고 합니다. 그니까 현재 접근방식은 에러가 나기 쉽습니다.

 


A new approach

그래서 완전 새로운 방식으로 접근해서 나온 것이 Diffable Data Source 입니다.

이 방식은 UI State가 변하게 되면 어떤 것이 변했는지 찾아내는 diff 작업이 일어나 계산해주고 애니메이션을 주어 UI를 업데이트해줍니다. (performBatchUpdate 쓸 때랑 다르게 개발자 추가 작업 X)

 

Diffable Data Source는 snapshot 개념을 도입합니다.

snapshot

  • UI 상태의 truth (Truth of UI state 직독직해)
  • section과 item에 대해 고유한 identier를 가짐 (Hashable 프로토콜 채택중)
  • indexPath 대신에 고유한 identifier를 이용해서 데이터 업데이트

(WWDC 예제가 최고)

snapshot에 대한 예제를 보면 아래와 같습니다.

FOO BAR BIF 를 가진 현재 snapshot이 있는데 어떠한 요청(?)에 의하여 BAR FOO BAZ를 가진 새로운 snapshot이 만들어졌습니다. 그리고 apply 메소드 하나로 새로운 snapshot이 적용됩니다.

한마디로, snapshot은 controller와 UI가 가지고 있던 UI State를 하나로 합친 것이라고 보면 되겠네요


Demos 중 한개만

이제 어떻게 사용하는지 알아보겠습니다.

알아보기 전에 사용 할 두가지 클래스에 대해서 먼저 알아보면,

 

NSDiffableDataSourceSnapshot은 Generic Class 타입이고 generic 타입 두개 모두 hashable해야합니다.

 

UICollectionViewDiffableDataSource 또한 Generic Class 타입이고 UICollectionViewDataSource를 채택중입니다. (UITableViewDiffableDataSource도 마찬가지입니다)

사용방법 

1. Diffable Data Source 생성 및 collectionView와 연결 그리고 configure cell provider

Diffable Data Source을 생성하고 collectionView에 포인터를 연결해줍니다.

생성을 할 때 Section과 String은 Hashable 해야하는데 Swift의 Primitive Type들은 다 hashable하고 Section은 모든 case와 associated value가 hashable을 준수하면 자동으로 설정된다고 합니다.

enum Section: CaseIterable {
    case main
}

주의사항: 디퍼블을 생성할 때 Generic 타입은 Hashable을 무조건 만족해야합니다. 위와 같이 단순히 String만 넘겨준다면 ["ABC", "ABC"] 같은 경우 중복되기 때문에 hashable을 만족하지 못합니다. 이 때는 UUID를 이용하여 hashable을 만족시킬 수 있습니다. 참고 → https://zeddios.tistory.com/1241

struct DJ: Hashable { 
	let id = UUID() 
	var name: String
}

cellProvider는 cellforItemAt IndexPath메소드 부분과 동일합니다. 기존 방식과 다른 점은 items[indexPath]와 같이 접근하였다면 identifier에 item이 담겨있습니다.

 

다른 방법 :

위 방법은 cell을 미리 register하고 cellProvider에서 configure을 해준 방식이라면 register와 configure을 같이 해주는 방법이 있습니다.

 

2. 새로운 snpshot 생성 및 적용

 

변경된 데이터에 대한 snapshot을 생성하고 채워주고 apply 해주면 끝입니다.

이렇게 되면 아래왼쪽과 같이 애니메이션이 적용된 UI 업데이트를 볼 수 있습니다.

오른쪽은 animatingDifferences를 false로 한 결과입니다.

 

화면 기록 2021-11-23 오전 4.22.42.mov
1.73MB
화면 기록 2021-11-23 오전 4.23.39.mov
1.28MB


Considerations

이제는 디퍼블 데이터 소스와 스냅샷을 지원하는 API들에 대해서 알아보겠습니다.

 

Snapshot & Indetnfier

1. snpshot

// 1
let snapshot = NSDiffableDataSourceSnapshot<Section, UUID>()
// 2
let snapshot = dataSource.snapshot()

snapshot을 생성하거나 기존의 dataSource에서 커피하여 가져와서 사용합니다. 또 item의 개수나 section, identifier의 개수는 얼마나 될까? 라는 것도 궁금하면 NSDiffableDataSourceSnapshot에 가보시면 많은 API들이 있다고 합니다.

 

indexPath이 필요없는 snapshot API

 

insertItems (beforeItem/afterItem): 특정 identifier 앞 혹은 뒤에 삽입하는 메소드

appendItems : 기존의 identifiers 뒤에 이어붙이는 메소드

delete , move .. 다할수있다

 

2. Identifier

Unique 해야함

Hashable 프로토콜 채택해야함

만약 data model에 같은 값이 있다면 이는 hashable하지 못하기 떄문에 UUID를 이용한다.

 

3. IndexPath based API

didSelectItemAt 등과 같이 indexPath를 기반으로 만들어진 API들이 많이 존재한다.

IndexPath → Identifier, Identifier → IndexPath 로 전환하는 API 제공한다.

 


마지막으로 diff작업에 대한 성능에 대해서 알아보겠습니다만 사실 잘 이해하지 못했습니다.

Performance

이 모든 diff 작업은 O(N) 시간이 걸린다고 합니다. 즉 item이 추가될수록 작업시간은 선형적으로 늘어난다고 합니다.

또 이렇게도 나오는데 여기부턴 잘 이해하지 못했는데,,,

item들이 여러개가 있으면, Apply 메소드를 background queue에서 부르는게 더 안전하다고 합니다.

여기서 주의사항은 background queue에서 apply메소드를 불렀다면, 일관되게 불러주라고 합니다. background queue에서 불렀다가, main queue에서 불렀다가 이러지 말랍니다.. ㅎㅎ


 

내용을 전부 정리하면 아래와 같습니다.

 

1. 디퍼블은 새로운 개념인 snapshot을 이용하여 UI state를 더 쉽게 관리하기 위해서 나온 것입니다.

2. snapshot은 indexPath를 사용하지 않고 고유한 section과 item identifier를 이용하여 UI state를 capture화시킨 것입니다.

3. UICollectionView를 업데이트 할 때, 새로운 snapshot을 만들고, 이를 새로운 UI state로 configure하고 apply를 하면 됩니다.

4. Diffable Data Source는 이전 상태와 다음 상태의 차이를 계산하고 자동으로 애니메이션을 넣어준다고 보면 됩니다. (reloadData와 차이점)

5. 디퍼블은 기존 방식보다 속도도 빠르고 (O(n)) 업데이트시 안정성이 있고 코드 작성도 쉽습니다.