제목: Boost Smooth Scrolling with iOS 10 Pre-Fetching API



이전 포스트에서 우리는 iOS 모바일 앱에서 부드러운 스크롤링을 달성하기위한 일반적인 전략을 살펴보았다. 이 전략을 적용시키는 주된 목표는 버벅이는 스크롤링(choppy scrolling)을 피하는 것이고 이런 일반적인 이슈는 사용자 경험에 부정적인 형향을 끼친다. 이런 작업으로 개발자들에게 도움이 되기 위해, 애플은 iOS10에서 UICollectionView를 매우 유용한 변화를 만들었다. 그러나 이렇게 새로이 소개된 기능을 검토하기전에, 우리를 위해 어떤 것이 필요한지 설명하면서 시작해보자.

어떤 이유로 스크롤링이 거칠게 되는가?
이따금씩 앱이 거친 스크롤이 될때를 본적이 있는가? 그 대답이 "그렇다"라면, 스크롤을 빠르게 하려고하는데 앱의 컨텐트가 더듬거리며 나올때 얼마나 실망하는지 경험해보았을 것이다. 여러분은 이런 거친 스크롤 동작이 어떤 이유에서 나왔는지 스스로에게 물어보았을 것이고, 이것이 나쁜 사용자 경험을 따라오게 한다.

짧은 대답은: 앱의 프레임이 떨어질때이다. 그러나 정확히 무슨 뜻일까?

부드러운 스크롤을 유지하려면, 앱은 안정적으로 60 FPS(Frame Per Second)로 표시해야한다. 다른말로 해보자면, 앱은 1초에 60번 컨텐트를 갱신할 필요가 있다. 이 의미는 각 프레임은 대략 16ms만에 렌더링 된다는 의미이다. 운이 나쁠 경우, 할당된 시간보다 더 많이 걸리고, 다음 프레임에 보여줄 데이터가 없어서 앱의 "프레임이 떨어지게" 된다. 이런 불행한 시나리오는 아래 다이어그램으로 표현할 수 있다. 파란색 표시는 그리는 작업을 지시하고 그것들의 생각은 렌더링을 완료하는데 필요한 시간을 표시한다. 여기서 볼 수 있듯, 두번째 프레임에서 우리는 할당된 시간(~16ms)보다 많은 작업이 드는 렌더링 이벤트를 가지는데, 결과로서 세번째 프레임이 드롭된다.


갱신 작업에 쓰인 CPU 시간의 뷰 포인트로부터 같은 시나리오를 보여줄 수 있다. 아래 그래프에서 위로 튀어올라가있는 부분은 앱이 현재 컨텐트를 갱신하기위해 예상했던 16ms보다 더 걸릴때, 프레임이 떨어지는 현상을 나타낸다.


좋은 사용자 경험을 달성하기 위해서는, 갱신 시간을 언제나 16ms 최대치보다 작도록 만들어야한다. 이상적으로는 훌륭한 사용자 경험(한번만이 아닌)을 만들고 싶으므로, 각 갱신 시간은 아래처럼 될 수 있다.
  • 지속적으로 허용된 시간(16ms)보다 아래로한다.
  • 가능한 다른 작업에 사용될 수 있는 CPU 시간을 절약한다.
 프레임을 떨어뜨리는 가장 일반적인 이유는 메인스레드에서 셀의 무거운 데이터 모델을 불러오는 것이다. 이 시나리오의 일반적인 예시는 다음과 같다.
  • URL을통해 이미지를 불러오기.
  • 데이터베이스나 코어데이터로부터 항목에 접근하기.

애플은 iOS10에서 셀들이 불러와지고 표시되는 방법의 최적화를 소개했었다. iOS10으로 증진시킬수 있는 방법을 보고, 부드러운 스크롤링의 사용자 경험을 만들기위해 개발자들이 어떠헥 더 쉽게 됐는지 보다.

iOS9에서의 셀 라이프사이클
UICollectionViewCell의 라이프사이클은 아래처럼 보여줄 수 있다.


컬랙션뷰와 그 셀 사이의 주된 인터렉션은 다음과 같다.
  • 컬랙션뷰는 보여지게될 셀을 위한 컨텐트를 필요로하고 있다. 그 셀은 visible 필드에 들어가는 것에 대한 것이다. collectionView(_ :cellForItemAt:)
  • 컬랙션뷰는 셀을 표시하는것을 요구하고 있다. 그 셀은 단지 visible 필드에 들어가있다. collectionView(_ :willDisplay: forItemAt:)
  • 컬랙션뷰는 셀을 제거하는 중이다. 그 셀은 visible 필드의 바깥에 있다. collectionView(didEndDisplaying: forItemAt:)

iOS10에서의 셀 라이프사이클
iOS10에서 셀의 라이프사이클은 iOS9에서와 거의 비슷하다. 그러나 몇가지 중요한 다른점이 존재한다.

첫번째로 다른점은 OS가 collectionView(_ :cellForItemAt:)를 사용하는것보다 훨씬 쉽게 호출한다는 것이다. 이것은 두가지를 의미한다.
  • 셀을 불러오는 무거운 작업은 셀이 표시되야한다는 필요가 있기 전에 완료될 수 있다.
  • 셀은 결국 표시되지 않을 수 있다. (visible 필드에 들어가지 않을 수 있기 때문에)

두번째로 다른점은 셀이 visible 필드에서 떠날때 그 안에서 일어나는 것이다. iOS10은 보통 collectionView(didEndDisplaying:forItemAt:)이 호출되는데, 셀은 즉시 재활용되지 않는다. OS는 사용자가 스크롤 방향을 뒤집는 경우를 대비해 몇개를 가지고 있는다. 그런 일이 일어나면 셀은 여전히 사용가능하고 컨텐트를 다시 로드할 필요 없이 (collectionView(_ :willDisplay: forItemAt:)가 호출되며) 다시 표시될 수 있다.


iOS9에서 일어나는 것과 비교해보자. 여러분도 알아차릴 것인데, 특정 유스케이스에대해 셀을 불러오는 무거운 작업(collectionView(_ :cellForItemAt:))은 더이상 필요없다. 이 최적화는 사용자가 빠르게 스크롤 할때나 스크롤 방향을 바꿀 때 모두 셀을 빠르게 렌더링할 수 있게 해준다.

세번째로 iOS9와 iOS10간에 중요하게 다른점은 여러행의 레이아웃으로된 컬랙션뷰에서 셀을 로드하는 방법이다.



iOS10은 visible 필드로 들어오는 각 셀마다 분리되어 로드된다(collectionView(_:cellForItemAt:)). 셀 라이프사이클을 설명하는동안 보았듯, 이런 일은 각 셀이 실제로 표시되여햘 때보다 훨씬 쉽다. OS가 다른 요청을 처리하고 셀 로드를 배치하여 최적화의 문을 열게되었다.

셀의 열이 visible 필드로 들어오면, 셀은 한 배치(single batch) (동시에 모든 열을 위해 collectionView(_ :willDisplay: forItemAt:)를 호출한다.)처럼 표시되는데, 이 작업은 CPU 사이클의 주기에서 아주 비싸지 않기 때문이다(적어도 셀 컨텐트를 불러오는 것과 비교해서).

프리-패칭하는 것은, 여러행의 레이아웃을 불러오는 것에 대한 이런 차이점은 iOS10의 UICollectionView 최적화에서 가장 중요한 점이다. 좀 더 세부적인 내용을 살펴보자.

프리-패칭 API
iOS10을 발표할때, 애플은 프리-패칭(Pre-Fetching)을 적용한 기술(Adaptive Technology)로서 소개했었다. 프리-패칭하는것은 스크롤 퍼포먼스 증진의 최적화를 위해 사용자들이 앱과 인터렉션하여 이점을 취하려고 할것이라는 의미다. 예를들어 이런 새로운 기술은 새로운 셀을 불러오기 위해(프리-패칭) 비어있는 시간을 찾아볼 것이다(사용자가 느리게 스크롤할때나 스크롤을 더이상 하지 않을때). 사용자 스크롤 패턴에따라 퍼포먼스같은 것을 실하기위한 프리-패칭의 기회를 더 (혹은 덜) 얻을 수 있다.

가능한 API를 검도하기 전에, 이 기술로 작업할 수 있는 최고의 실행을 살펴보자. 프리-패칭하는 것의 최고의 이점을 얻기위해 셀 컨텐트를 세팅하는 작업들은 반드시 collectionView(_ :cellForItemAt:)에서 실행되어야한다. collectionView(_ :willDisplay:forItemAt:)에서 실행되는 작업들과 collectionView(didEndDisplaying:forItemAt:)에서 실행되는 작업들은 최소화해야하고 CPU가 부담되지 않도록 만들어야한다. 더 나은것은, 이 라이프사이클 이벤트에 아무 작업도 실행시키지 않는게 더 최적화된 것일 것이다! 또한 collectionView(_ :cellForItemAt:)이 셀을 위해 호출될지라도 셀이 절때 표시되지 않을 수 있다는 가능성을 마음에 새겨두자.

어떤 굉장히 좋은 소식은 프리-패칭하는것이 iOS10에서 컴파일된 앱에는 디폴트로 되었다는 것이다. 그러나 UICollectionView의 isPrefetchingEnabled 프로퍼티를 false로 설정하여 이 기능을 끌 수도 있다. 우리가 구현해놓은 컬랙션뷰의 코드를 바꿀 필요가 없다는 뜻이다. 프리-패칭하는 기능의 이점을 얻기위해 취해야할 유일한 옵션은 Pre-Fetching API를 시행하는 것이다.

프리-패칭하는 API와 UICollectionView
UICollectionView를위한 프리-패칭하는 API는 UICollectionViewDataSourcePrefetching 프로토콜에 정의되있다. 이 API는 두가지 메소드를 따르도록 정의되있다.

collectionView(_:prefetchItemsAt:)(필수)—이 메소드는 [IndexPath] 파라미터에의해 지정된 셀의 데이터를 비동기적 로딩을 초기화해준다. 이 비동기 로딩은 Grand Central Dispatch를 통해 실행되거나 OperationQueue를 통해 실행될 수 있다. 이 메소드를 구현할때 중요한 점은 데이터 로딩의 burden을 메인큐에서 백그라운드큐로 이동하는 코드를 작성하는 것이다. 이것의 목표는, 새로운것을 표시하는 중요한 작업을 실행하는 많은 시간을 백그라운드 큐에서 보내도록 하기위해 데이터 로딩 부담을 메인큐에서 백그라운드큐로 넘겨 작업량을 줄이는데 있다.

collectionView(_:cancelPrefetchingForItemsAt:)(선택)—이 메소드는 더이상 필요없는 셀을 [IndexPath] 파라미터로 지정하여 데이터를 소통한다. 이 메소드를 구현하면 필요에따라 데이터 로딩이 되지 못한 것을 취소하는데 사용할 수 있고, 불필요한 작업을 취소하여 CPU 시간을 절감하는 좋은 방법이 된다(일반적으로 사용자가 스크롤 방향을 바꾸기 때문에).

우리가 이전에 말했듯, 프리-패칭하는것은 적용가능한 기술이다. 그러므로 위의 메소드는 앱과 인터렉트하는 사용자의 방법에따라 다르게 유발된다. 이것의 한 결말은 collectionView(_:prefetchItemsAt:)메소드가 컬랙션뷰의 셀마다 호출되지 않는다. 이 말은 collectionView(_:prefetchItemsAt:)를통해 셀을 로딩할때, 앱은 아래의 모든 시나리오를 다룰 수 있다.
  • 데이터가 미리 패치되었고 표시되기위해 준비되었다.
  • 데이터가 현재 패치되는 중이고 표시되기위해 준비되진 않았다.
  • 데이터가 아직 요청되지도 않았다.

프리-패칭하는 API와 UITableView
또한 iOS10은 UITableView를 위한 프리_패칭하는 것도 소개했다. UICollectionView에서 설명했던 모든 주요 개념들이 비슷한 방법으로 UITableView에 적용된다. UITableView를위한 프리-패칭하는 기술은 UITableViewPrefetchingDataSource 프로토콜에 정의되있다. 이 API는 아래 두 메소드를 정의한다.

tableView(_:prefetchRowsAt:)(필수)—이 메소드는 [IndexPath] 파라미터에의해 지정된 셀의 데이터를 비동기적 로딩을 초기화해준다.

tableView(_:cancelPrefetchingForRowsAt:)(선택)—이 메소드는 더이상 필요없는 셀을 [IndexPath] 파라미터로 지정하여 데이터를 소통한다.

UICollectionView 프리-패칭하는 API 메소드에대해 소개한 생각들은 같은 방법으로 비슷한 각 UITableView 프리-패칭하는 API 메소드에 적용된다.

결론
나는 iOS10에서 프리-패칭하는 기능을 적용시키기위해 UITableView와 UICollectionView의 샘플 코드를 수정해놓았다. 여기에서 확인할 수 있다.

이 포스트에서는, UICollectionView와 UITableView에대해 부드러운 스크롤 증진이 가능한 구현을 검토했다. 특히 새로운 OS 최적화에대한 모든 이점을 취할 수 있고 우리의 모바일 앱과 인터렉트할때 최고의 사용자 경험을 제공할 수 있는 특정 프리-패칭하는 API를 구현하여 보았다.

추가적인 링크




이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,