iOS13で新しく追加されたMetricKitがとてもすごい件

この記事は、[ZOZOテクノロジーズ #5AdventCalendar2019](https://qiita.com/advent-calendar/2019/zozo_tech5)の記事です。

昨日は[@EnKUMA](https://qiita.com/EnKUMA)さんの[「GKE上でgcloudコマンドを叩く為のPodをDeployしたら詰まった話」](https://qiita.com/EnKUMA/items/268d3b60dfca5c8da664)の記事でした。

ZOZOテクノロジーズ iOSエンジニアの[@ahiru](https://twitter.com/ahiru_starrr)です。

本稿ではiOS13 Xcode11より新しく追加されたフレームワークの一つであるMetricKitについて調べたことなどをまとめていきます。

WWDC 2019の「[Improving Battery Life and Performance](https://developer.apple.com/videos/play/wwdc2019/417/)」にてMetricKitの詳細が説明されておりますので、このセッションをベースにMetricKitとは何か、どんなことができるのかなどを中心に書いていきます。

# What's MetricKit?

[Appleのドキュメント](https://developer.apple.com/documentation/metrickit)を見てみると以下のように記述されています。

> Aggregate and analyze per-device reports on power and performance metrics

MetricKitはデバイス上のアプリの電力やパフォーマンスなどのメトリックを受け取るためのフレームワークです。

MetricKitを使うことで**バッテリーとパフォーマンスへの影響を定量化**できるようになります。

すなわち、アプリのパフォーマンスを向上させる上でどの機能がボトルネックになっているのか、何が原因で起動時間が遅いのかなどの原因の特定をかなり簡単に調べることができるようになりました。

# メトリックスの種類

**Battery Metrics**と**Performance Metrics**の2のメトリックが存在します。

## Battery Metrics
- Processing
- Location
- Display
- Networking
- Accessories(Bluetooth)
- Multi Media
- Camera

## Performance Metrics
- Hangs
- Disk
- Application Launch
- Memory
- Custom Intervals

# メトリックスを取得するための3つのツール

iOS13 Xcode11よりメトリックスを収集するための3つの機能が追加されました。

下記で紹介する3つの機能をうまく使うことで各開発段階(開発・テスト / ベータ版 / リリース版)のそれぞれでメトリックスを収集することが可能となります。

## 1. XCTest Metrics

これまでもXCTestからメトリックスを取得することは可能でしたが、取得できるメトリックが増えました。

UIテスト・ユニットテスト両方で使用可能です。

### measure(metrics: [XCTMetric], block: () -> Void)

- 【 iOS13 Xcode11未満 】では実行時間のみが計測可能だった

```swift
func testPhotoUploadPerformance() {
        let app = XCUIApplication()
        // 実行にかかる時間を測定
        measure() {
            app.buttons["Apply Effect"].tap()
            app.dialogs["alert"].buttons["OK"].tap()
        }
}
```

- 【 iOS13 Xcode11以降 】では取得したいメトリックを指定することが可能 (取得できるメトリックについては[こちら](https://developer.apple.com/documentation/metrickit#overview)を参照)

```swift
func testPhotoUploadPerformance() {
        let app = XCUIApplication()
        // 実行にかかる時間 / メモリ使用量 / CPU使用率 を測定する
        measure(metrics: [XCTClockMetric(),
                          XCTMemoryMetric(application: app),
                          XCTCPUMetric(application: app)]) {
                            app.buttons["Apply Effect"].tap()
                            app.dialogs["alert"].buttons["OK"].tap()
        }
}
```

### testApplicationLaunchTime

また、新しいUIテストの各ターゲットにはアプリケーションの起動テストが自動でテストを追加されるようになりました。

```swift
func testApplicationLaunchTime() {
        measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) {
            XCUIApplication().launch()
        }
}
```

実際にこのテストを実行してみた方はわかると思うのですが、5回くらいアプリが立ち上がります。

その後テストの実行ボタンの下に詳細ボタンが表示されるのでそれをクリックすると下のようなポップアップが表示されます。
<img width="274" alt="スクリーンショット 2019-12-22 2.06.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122157/720bd785-f7b3-d68a-94bb-f1c5aff0d99f.png">

5回の起動の平均時間がAverageとして表示され、それをBaselineに設定することができます。いわば、その**アプリの起動時間の基準値**です。

自身が開発中のアプリに新しい機能の追加などを行った際に、起動時間が基準値よりも遅くなった場合にはその機能のパフォーマンスが適切とは言えません。
パフォーマンスの側面からリグレッションテストが可能となりました。

パフォーマンステストを行う際の注意点として以下の点がセッションの中で挙げられていました。

- 負荷がかかるのでデバッガーは加えない
- 全ての診断オプションは切っておく
- 独立したスキームを使うかテストプラン機能を使うことで診断オプションは簡単にオフにできる


## 2. MetricKit

メトリックを収集するためのオンデバイスフレームワークです。

各ユーザによるアプリケーション使用時の状況を理解するのに役立ちます。

主にベータ版・リリース版に対して詳細なメトリックスを収集するために使用します。

### 実装方法

実装方法はとても簡単です。

クラスを作成しメトリックスの受信を許可するだけです。するとデータ(メトリックス)の収集が開始されます。

```swift
class MySubscriber: NSObject, MXMetricManagerSubscriber {
    
    var metricManager: MXMetricManager?
    override init() {
        super.init()
        
        metricManager = MXMetricManager.shared
        // メトリックスの受信を許可する
        metricManager?.add(self)
    }
    
    deinit {
        metricManager?.remove(self)
    }

    func didReceive(_ payloads: [MXMetricPayload]) {
        for metricPayload in payloads {
            // 各自、ペイロードをサーバーに送ったり、ファイルに保存したりする
        }
    }
}
```

MetricKitを使うことでアプリがいつどこでどれくらい使われたのかを簡単に知ることができます。
<img width="940" alt="スクリーンショット 2019-12-22 2.57.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122157/ad213fe3-d73f-ddc8-6844-43a111bd94aa.png">
24時間が経過するとそのサマリが作成されてデバイスに返されます。上記の実装をすると1日1回自動でメトリックスの収集が行われます。

#### mxSignPost

アプリ内のある機能やあるアクションに関するメトリックスを収集することができます。

実装方法はとても簡単で対象の箇所をmxsignPostで囲むだけです。

```swift
let photosLogHandle: OSLog = MXMetricManager.makeLogHandle(category: "Photos")
        
mxSignpost(.begin, log: photosLogHandle, name: "SavePhoto")

// パフォーマンス計測したい処理をここに記述
        
mxSignpost(.end, log: photosLogHandle, name: "SavePhoto")
```

上記のように実装すると、MetricKitが自動でメトリックスを収集してくれます。


## 3. Xcode Metrics Organizer

コードを変更することなくOrganizerから集約されたデータの確認ができます。

主にリリース版で生じた問題を収集するために使用します。

Window -> Organizer -> Metricsのタブより確認ができます。

個人開発のアプリで確認してみた結果がこちらです。(アップデートしたら起動時間が短縮されてて嬉しい...!!!)
<img width="1427" alt="スクリーンショット 2019-12-22 3.21.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122157/d92e3060-3d29-40c1-26a6-067554a60bf3.png">
起動時間やバッテリーの使用量などのデータを見ることができます。

バージョン毎に確認できるので、テストの時はパフォーマンスに問題なかったのにリリース版ではパフォーマンスが悪くなっているなどの確認も簡単に行えます。

いろんなメトリックがグラフで可視化されていて面白いです。

皆さんの携わっているプロダクトでもぜひ見てみてください。

# まとめ

WWDC 2019は内容が盛り沢山だったためMetrics周りに言及された国内の記事はあまり見かけませんが、実は結構な進化を遂げていました。

アプリのパフォーマンス向上はアプリ開発者なら誰しもが意識するところだと思います。

XCTestでいろんなメトリックからパフォーマンス計測を簡単に行えたり、MetricKitを使ってリリース版のメトリックスを簡単に取得できるのはハイクオリティのアプリケーションを作る上でとても大きな恩恵だと思います。

MetricKitを使って質の高いアプリを作っていきましょう!!