UITableViewDataSourcePrefetching 프로토콜

WWDC 2018의 What’s New in Cocoa Touch를 보던 중 스크롤 성능에 관한 주제가 나오게 되었고 iOS 10에서 나왔던 UITableView와 UICollectionView에서 사용할 수 있는 prefetch 관련 메서드 이야기가 나와 공부하게 되었다.

Background

사용자는 스크롤이 부드럽게 되기를 기대한다. 하지만 셀을 구성하는 데 무거운 작업이 필요할 경우 스크롤이 늦어지는 데 이를 프레임 드랍이라고 한다. WWDC 2016의 What’s New in UICollectionView in iOS 10에 따르면 부드러운 스크롤링이 되기 위해서는 앱이 초당 60 프레임(60 FPS)을 제공해야 한다. 다시 말하면, 각 프레임이 약 16 ms마다 그려져야 한다는 것이다. 하지만 이것을 제공하지 못하는 경우가 생기는데 이 때, 스크롤링이 버벅이게 된다.

Solution

iOS 10에는 iOS 9과 다른 점이 몇 가지 있다.

  1. cell이 보이지 않게 되었을 때 바로 재사용 큐에 넣지 않는다: iOS 9까지는 보이지 않게 된 셀을 바로 재사용 큐에 넣었었다. 이렇게 될 경우 사용자가 위로 스크롤을 할 경우 데이터를 불러오는 작업을 다시 수행해야 한다는 단점이 생긴다. 이 작업은 무거운 작업이기 때문에 iOS 10에서는 조금 더 시간을 가진 뒤에 셀을 재사용 큐에 넣는 방식을 택했다. 물론 didEndDisplayingCell은 전과 같이 호출되지만 셀이 보이지 않다가 다시 보일 경우 다시 cellForRowAt이 불리지 않고 willDisplayCell만 불리게 된다.
  2. 멀티 칼럼의 경우 각각의 셀이 독립적으로 보이게 된다.

iOS 10에서 스크롤링을 할 경우 이러한 것들이 반영이 되어 iOS 9보다 훨씬 나은 스크롤링을 보여준다. 그러나 애플은 한 걸음 더 나아가 Prefetching 기술을 소개했다.

UITableViewDataSourcePrefetching?

iOS 10 이상부터 사용할 수 있으며 셀을 구성하는 데이터를 불러오는 작업을 미리 할 수 있는 프로토콜이다.

셀을 구성하는 데이터를 불러오는 작업이 무거울 경우, cellForRowAt에서 이것을 다 하면 무리가 갈 것이다. 셀을 구성하는 작업을 메인 스레드에서 다 하고 있는데 그 작업을 백그라운드로 옮겨와 처리하자는 것이 핵심이다.

여기서 중요한 것은 이것이 기존의 데이터 모델을 구성하는 로직을 대체하는 것이 아니라는 것이다. 대신 기존의 비동기 로직을 도와주고 테이블 뷰와 컬렉션 뷰가 컨텐츠를 불러올 때 추가적인 힌트를 제공한다고 생각하면 된다.

메서드

두 가지 핵심 메서드가 있다.

tableView(_:prefetchRowsAt:)

[IndexPath]에 해당되는 셀에 필요한 데이터를 미리 받아온다. 주로 GCD나 Operation으로 비동기 처리 작업을 명시한다. 필수적으로 구현해야 한다.

tableView(_:cancelPrefetchingForRowsAt:)

더 이상 prefetch가 필요치 않은 셀들에 대해 작업을 취소하는 메서드이다. 주로 스크롤의 방향을 반대로 할 때 작업을 취소한다. 옵셔널하게 구현한다.

이 기술은 보조 기술(Adaptive Technology)이기 때문에 불리지 않을 수도 있다. 예를 들어 사용자가 매우 빠른 속도로 스크롤하는 경우 미리 데이터를 받아 놓을 시간은 없을 것이다. 그래서 cellForRowAt 메서드에서 다음과 같은 3가지 상황을 모두 처리해줘야 한다.

  • prefetch가 끝나 보여줄 준비가 된 상황
  • prefetch를 하는 중이라 보여줄 준비가 되지 않은 상황
  • 요청조차 되지 않은 상황

즉, 표시할 데이터가 있는지 확인한 뒤 있다면 바로 보여주고 없다면 새로 요청을 하여 셀을 업데이트 해야 한다.

iOS 12에서의 변경점

이번 WWDC에서 발표한 iOS 12는 성능 개선에 중점을 두고 있는데 이 프로토콜에도 개선점이 생겼다. 이 프로토콜 자체도 성능 개선을 위한 프로토콜이지만 한 가지 문제가 있었다. What’s New in Cocoa Touch - WWDC 2018 Session 202에 따르면 tableView(_:cellForRowAt:)에서 표시할 작업과 tableView(_:cancelPrefetchingForRowsAt:)에서 아직 필요하지 않은 미래의 데이터를 가져오는 작업이 동시에 이루어지기 때문에 CPU에게 부담을 준다는 것이다.

위 그림처럼 셀을 구성하고 그리는 작업과 이후에 사용될 데이터를 미리 불러오기 때문에 CPU에 좀 더 많은 부담을 주게 되어 프레임 저하를 일으킬 수 있다는 것이다. 그래서 iOS 12에서는 prefetching 작업이 Concurrent하게 이루어지지 않고 현재 셀 구성 작업 후 Serial하게 이루어지도록 변경되었다.

iOS 12를 적용하면 자동적으로 이러한 변경점과 그 밖의 성능 개선점이 적용이 된다. 위에서 언급한 세션에서는 개발을 할 때 좋은 스크롤링 성능이 나올 수 있는 가이드 라인을 제공한다.

  • Prefetching API를 고려하라: 데이터를 미리 준비시킬 수 있다.
  • 최대한 셀 구성 작업을 가볍게 하는 것이 좋다: iOS 12에서는 필요에 따라 언제 CPU 성능을 끌어올릴지 판단하지만 그래도 가능한한 셀 구성 작업을 줄이는 것이 좋다.

Reference