[iOSDC 2016] iOS10のCollectionViewからライフサイクルが変わったので遊んでみた

こんにちは。16卒新卒iOSエンジニアの井原です。

8/20(木)にiOSDCが開催されました。弊社はスポンサーの1つとして協力させていただきました。

今回はその中でも興味のひかれた発表をピックアップし、その紹介をします。またいくつか気になった点については検証を行いましたので、加えてその結果について説明をしたいと思います。

iOSDCとは

iOSDC (iOS Developers Conference Japan) は、iOSとその周辺技術に関するエンジニアのためのカンファレンスです。
iOS関連のカンファレンスを待っていた皆様、お待たせしました。ついにやってきました。

2016年8月20日はiOSエンジニアのお祭りです!
日本中、世界中から公募されたスピーカーがキレッキレのトークを繰り広げます。
トークは「iOSエンジニアが聞いて面白ければ何でもOK」という基準で選定されます。
iOSやSwiftといった王道テーマから、エモい話、デザインの話などもあるかもしれません。

https://iosdc.jp/2016/

iOS10時代のCollectionView最新つかいこなし

iOS10でのCollectionView(UICollectionView, UITableView)に関して、TachibanaKaoruさんより発表がありました。CollectionViewは使用頻度が比較的高いと思われるViewですが、iOS10での変更やそれに伴う注意点があるとのことです。

iOS10からの変更点として、以下の2点について触れられていました。

  • CollectionViewでストアされるセルの数が1画面から1.5画面分に増えた
  • CollectionViewでCellの表示前に呼ばれるprefetch機能が追加された

これらによりスクロール時のパフォーマンスは格段に向上したとのことです。しかしながらストアされるセルの数が増えたためメモリ使用量が増えてしまいます。そこで、iOS10における変更点に依存しないパフォーマンス向上のために行うべきTipsも紹介してくださいました。

この発表を聞いて、個人的に気になったのは以下の2点でした。

  1. iOS10上でストアされるセルの数をiOS9以下と同様の1画面分にできるのか
  2. prefetchが呼ばれるタイミングはいつなのか

今回はUITableViewを例に検証しました。

1. iOS10上でストアされるセルの数をiOS9以下と同様の1画面分にできるのか

メモリに不安があるiPhone 4sのような端末では、セルが複雑になると軽量化のためセルのストア数をiOS9以下と同じ挙動にしたいといったケースが考えられると思います。例えば、セルの初期生成で生成されるセル数が増えたためページ遷移が重くなってしまうといったケースです。

発表スライドより抜粋
発表スライドより抜粋

Xcode8 beta6でCollectionViewの挙動を確認したところ、想定の挙動と違いました。

  • beta6: 1画面分のセルがストアされている
  • beta5: 1.5画面分のセルがストアされている

発表者のTachibanaKaoruさんに連絡を取り検証環境を確認したところ、Xcode8 beta5では1.5画面のセルがストアされていたそうです。beta6のRelease NotesにはCollectionViewに関する記述は見当たらなかったのですが、beta6ではiOS9以下における挙動と同様になっています。今後のバージョンアップの際にストアされるセル数を設定できるかを検証したいと思います。

2. prefetchが呼ばれるタイミングはいつなのか

iOS10では、画像のダウンロードなどの重たい処理を先に行うためのprefetch機能が追加されました。

// 対象のindexPathに対してprefetchが呼ばれる
func collectionView(_ collectionView: UICollectionView, prefetchRowsAt indexPaths: [IndexPath]) {
    ...
}
// 対象のindexPathに対してprefetchのキャンセルが呼ばれる
func collectionView(_ collectionView: UITcollectionViewbleView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
    ...
}
発表スライドより抜粋
発表スライドより抜粋(再掲)

prefetchが引数にとるindexPathsは複数のindexPathを含みます。複数ということもあって、どのように呼ばれるのかイメージが出来なかったため、実際に動かして検証してみました。

初回読み込み
1.初回読み込み
// 0番目のセルが生成
1. cellForRowAt: 0
2. cellForRowAt: 1
3. cellForRowAt: 2
4. cellForRowAt: 3
5. cellForRowAt: 4
6. cellForRowAt: 5
7. cellForRowAt: 6
// 1.5画面分のセルのindexPathが対象
8. prefetch: [7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
2.1 ゆっくり下にスクロール
2.1 ゆっくり下にスクロール
1. cellForRowAt: 7
2. prefetch: [17]
3. cellForRowAt: 8
4. prefetch: [18]
...
17. cellForRowAth: 15
18. prefetch: [25]
2.2 素早く下にスクロール
2.2 素早く下にスクロール
1. cellForRowAt: 7
2. prefetch: [17]
3. cellForRowAt: 8
4. cellForRowAt: 9
5. prefetch: [18, 19]
...
?. cellForRowAth: 13
?. cellForRowAth: 14
?. cellForRowAth: 15
?. prefetch: [23, 24, 25]
3. 上にスクロール
3. 上にスクロール
// prefetchのキャンセル処理
1. cancelPrefetch: [[25]]
2. prefetch: [8, 7, 6, 5, 4, 3, 2]
3. cellForRowAt: 8

prefetchの対象となるindexPathの取り扱いはユーザーの操作でかなり変わってしまいます。初回読み込み時や素早いスクロールやスクロールの方向が切り替わる時などに、prefetchは複数のindexPathを対象に呼ばれるので、描画処理等のメインスレッドで行なわれる重い処理は避けたほうが良さそうです。また、iOS9以下ではそもそもprefetchが呼ばれないため、prefetchで画像のダウンロードなどを行うようにした際に、iOS9以下では他のところでダウンロードしなければならないので注意が必要です。

まとめ

Xcode8はまだbetaバージョンなので、これからも変更されそうです。今後のバージョンアップでの変更もキャッチアップしていきたいと思います。