ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
XCTestを使ってアニメーションヒッチを除去する
アニメーションは、Appのユーザー体験を劇的に向上させ、直接操作しているような感覚を与え、自身が起こしたアクションがもたらす結果をより理解することに役立ちます。アニメーションヒッチはそのような経験を台無しにします。スクロールやアニメーションを円滑に行うために、割り込みを検知するXCTestの使用方法をお伝えします。あなたのAppを頼りにしている人々に影響が出る前に、不具合を見つけ出す方法もお伝えします。
リソース
関連ビデオ
WWDC21
WWDC20
-
ダウンロード
こんにちは WWDCへようこそ “XCTestを使って アニメーションヒッチを除去する” ソフトウェアエンジニアのタヌジャです アニメーションはアプリケーションにおいて 重要な要素です 戻るボタンをタップして 画面を移動するような軽い動作や 上下にスクロールする時のように 動作の中心となる場合もあります こうした動きに乱れがあると気になるので スムーズに反応させることは とても大切です ユーザーが気づく乱れを“ヒッチ”と呼びます ヒッチは1フレームが予想よりも遅れることで ユーザーの邪魔になり アプリケーションの 感性品質を落としかねません ヒッチが起きている状態を 1フレームずつ確認しましょう 3枚のフレームは予想どおりに表示され 指の動きと完全に合った なめらかな動作となります
ところが3枚目のフレームが残っていると 指と異なる動きになるのです
そして4枚目が表示されると 突然大きく動き 指の位置に戻ります このような動作は ぜひ避けたいものです そのためにはフレームが どう画面に表示されるかを理解しましょう iPhoneやiPadのフレームは通常60ヘルツで 1フレームごとに 16.67ミリ秒で更新しています iPad Proでは120ヘルツとなり 1フレームごとの更新は8.33ミリ秒です この更新はVSYNCと表されていて フレームの切り替え判断時に起こります ヒッチが発生するのは フレームがVSYNCを逃した時です ヒッチの深刻度は 画面上のフレームの遅れ具合で計測できます ここでは4枚目のフレームが 16.67ミリ秒遅れています
ヒッチの確認方法は2つ
ヒッチタイムはフレームの遅延時間を ミリ秒単位で表します また ミリ秒で表したヒッチ比率の出し方は ミリ秒単位の合計ヒッチタイムを 秒単位の他の継続時間で割ります 例えばテストの継続時間ですが ここでは ドロップフレームや 毎秒フレーム(fps)を計測してみましょう fpsは ずれが起きやすい絶対的な目標値です アニメーション実行中に テストが停止時間を含んでいれば 停止時間中にスワップされたフレームを 予測できないので fpsは役に立ちません
私たちはfpsの最大値を 意図的に目標としていません ゲームなら30fps 映像は24fpsが必要となるでしょう 電力や処理能力の理由から 時計 アプリケーションのアイコンの針は10fpsですが 信頼性を重要視し ヒッチタイムの目標は常にゼロにしています 比較が難しい場合もあります 1秒と10秒のテストでは 合計のヒッチタイムが比較できないのです そこで継続時間のヒッチをミリ秒にし 比率の値を正規化することで メトリックスを取得し 異なるテストの比較や エンドユーザーへの影響を想定できます これは エンドユーザーへの影響において 推奨する目標のヒッチ比率です
5ミリ秒以下であれば ユーザーエクスペリエンスに最適な比率で 5から10ミリ秒の範囲であれば ユーザーが気づくヒッチとなり検証が必要です
10ミリ秒以上なら ユーザーにとって 使いづらくなるので早急に対処してください iOS 14では ツールを使い開発と生産の ワークフローでヒッチが追跡できます XCTestはヒッチやアニメーションデータを ユニットやUIテストで収集し MetricKitやXcode Orgnizerは パフォーマンスメトリックスにアクセスできます 今回は XCTestを使い 開発ワークフローでヒッチを確認する方法です 生産ワークフローでの確認方法については MetricKitやXcode Organizerに関する セッションをご覧ください Xcode 11では XCTMetricを導入しました このメトリックスは計測する内容を指定します 現在 XCTMetricでテストできるのは クロック数や CPU メモリの使用率 os.signpostとストレージです Xcode 12にはアプリケーションの起動時間を計る メトリックスがあり メトリックスを書くための テンプレートもあります
ここで説明するXCTOSSignpostMetricは アニメーションテストに使うXCTMetricです Xcode 11では os.signpost intervalで時間を計測します Xcode 12で os.signpost intervalを使うと メトリックスやフレームレート フレーム数に関連するヒッチも取得できます
フレームレートやフレーム数とは 画面に表示される フレームの頻度や数を計測する値です ヒッチは先ほど お話ししましたね コード内でのヒッチの回数や テスト中にヒッチが継続した合計時間 コード内の時間をヒッチの合計時間で 割った比率を追跡できます このメトリックスの収集には os.signpost intervalの実行コードが必要です 方法は3つです non-animation intervalは時間だけを返し animation intervalは アニメーションメトリックスも値として返します Xcode 11なら non-animation os_signpost intervalを挿入するだけで 時間を返すことができます
Xcode 12では animation os.signpost intervalを指定し animationBeginインターフェイスを 使うだけです 1つの変更で既存のコードを animation interval実行用にでき アニメーションメトリックスを受け取れます
また 定義済みのUIKitに intervalを挿入することで 移動やスクロールのテストができます これは XCTOSSignpostMetricクラスでの サブメトリックスです サブメトリックスの1つを使った例を 見てみましょう XCTestでの性能テストです Meal Plannerをタップし食べ物のビューを表示 スワイプでスクロールします
テストで scrollDecelerationの サブメトリックスを計測しましょう
ブロック内にスワイプアップとあり Xcode 12ではスクロールの速度が変更できます
テストは問題ありませんが 改善点が見つかりました デフォルトではブロックでの性能の計測が 5回実行されます つまり現在の実装では 連続で5回スワイプアップする間に 別のコンテンツをスワイプしてしまいます
これを避けるため 実行中に アプリケーションをリセットしたいのです XCTMeasureOptionを使うことで 計測の収集を手動で停止できます 命令を受けた計測用のブロックがstopMesuring を呼び出し アプリケーションをリセット 書いたテストを実行しましょう その前に 性能への影響を取り除くため テストスキームの設定をいくつか修正します まず XCTestの性能テスト用に 別々のテストスキームがあることを確認
Build Configurationで Releaseを選び Debuggerを無効に
また Automatic Screenshotと Code Coverageも無効にします 最後に診断用の全オプションを 無効にしてください Runtime Sanitizationや Runtime API Checking Memory Management内にあります XCTestを実行し レポートUIで結果を確認しましょう アニメーションメトリックスのドロップダウンで Hitch Time Ratioを選びます
繰り返した5回の計測値が集積されました
ヒッチタイム比率は 平均1.2ミリ秒と表示されています
次に 表示された平均値の1.2ミリ秒を 基準として設定しましょう 今後このテストを実施した時に 比較するためです
では コードベースでヒッチの頻度を確認します XCTestを使い 万全な機能を顧客に提供しましょう
私が 料理の注文や配達アプリケーションの 開発担当者だと仮定してみます 別のメニューアイテムのビューを 実装したところです
ここに それぞれの料理画像を表示させ 見やすくしていきましょう 機能を加える前に 現在のアニメーション性能を計測します 新しい機能を追加した後に比較するためです
性能テスト用のスキームを作り 先ほどの内容に沿った設定を構成しました テストを書き出します
アプリケーションを起動し Meal Plannerをタップ スクロールするアニメーションを計測します
事前にテストをしたので結果を見てみましょう
ヒッチはゼロで 要求どおりのアニメーションになっています では新しい機能を加えてみましょう
まず 既に落とし込んでいる画像を 表示させるビューを設定します
次に 画像のサイズを確認して アプリケーションに合わせます 再度 性能テストを実行しましょう
XCTOSSignpostMetricを使うと os.signpost intervalが省略された時に 計測ブロックがリッスンします また 実行されたコードの メトリックスも収集します 他にも計測用ブロックは 異なるos.signpost intervalにも対応し scrollDecelerationや scrollDragging os.signpost intervalをリッスンできます スワイプアップと同じブロック内です では テストの終了後から続けます ヒッチ数が増加したようなので すぐに対処が必要です
問題は scaleAspectFitです 残りのUIの元となる メインスレッドの画像を書き直すため CPUで新しいピクセルを作り メモリを割り当てます
書き直した画像をGPUに渡す CoreAnimationの setContentModeなら 既存のピクセルを利用できるので メインスレッドでの作業量を軽減できます
テストを再実行し 問題が解決したか確認しましょう
アニメーションメトリックスのヒッチはゼロに 性能も要求どおりになりました
XCTestで リグレッションを起こす機能を 検出し修正することができたので 安心して顧客に届けることができます
では今日のおさらいです ヒッチはフレームが予想より遅く 画面に表示されることで 推奨の検証カテゴリーを使って ヒッチの状態を計測できます
そして XCTestを使った 開発ワークフローでのヒッチの確認です UIKitや os.signpost intervalでも 確認できます また 計測を繰り返す間にアプリケーションの コンテンツをリセットする方法や 不正確さを防ぐ スキーム構成も説明しました コードベースでヒッチを回避して スムーズなアニメーションを提供しましょう ご覧いただき ありがとうございました
-
-
6:35 - Create an animation os_signpost interval
os_signpost(.animationBegin, log: logHandle, name: "performAnimationInterval") os_signpost(.end, log: logHandle, name: "performAnimationInterval")
-
6:55 - Use a UIKit instrumented animation os_signpost interval
extension XCTOSSignpostMetric { open class var navigationTransitionMetric: XCTMetric { get } open class var customNavigationTransitionMetric: XCTMetric { get } open class var scrollDecelerationMetric: XCTMetric { get } open class var scrollDraggingMetric: XCTMetric { get } }
-
7:12 - Measure scrolling animation performance using a Performance XCTest
// Measure scrolling animation performance using a Performance XCTest func testScrollingAnimationPerformance() throws { app.launch() app.staticTexts["Meal Planner"].tap() let foodCollection = app.collectionViews.firstMatch measure(metrics: [XCTOSSignpostMetric.scrollDecelerationMetric]) { foodCollection.swipeUp(velocity: .fast) } }
-
8:02 - Reset the application state between runs
func testScrollingAnimationPerformance() throws { app.launch() app.staticTexts["Meal Planner"].tap() let foodCollection = app.collectionViews.firstMatch let measureOptions = XCTMeasureOptions() measureOptions.invocationOptions = [.manuallyStop] measure(metrics: [XCTOSSignpostMetric.scrollDecelerationMetric], options: measureOptions) { foodCollection.swipeUp(velocity: .fast) stopMeasuring() foodCollection.swipeDown(velocity: .fast) } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。