[iOS][HealthKit] HealthKit のセットアップ async/await 対応板

話題(?)の async/await を使うと、HealthKit への requestAuthorization がどうなるかを確認してみました。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterery beta 5
  • Xcode 13 beta5
  • iOS 15 beta

async/await 対応した HealthKit

Swift5.5 になって、Concurrency がサポートされました。

言語仕様として実装されただけでなく、Apple から提供されている多くの フレームワーク で Concurrency サポートが追加されました。

この記事では HealthKit のセットアップと使い方を確認します。

大きな変更点: async/await 対応

async/await 対応したことで、delegate や completionHandler でのやりとりを await を使った呼び出しに変えることができます。

HealthKit でもいくつか用意されている非同期の関数を呼び出すと 処理終了時に 設定した delegate や completionHandler が呼び出される仕組みでした。その部分を await を使った呼び出しに変えることができるようになります。

await を使った呼び出しに変更することで、機能的な相違はありません。
ですが、コード的には、delegate を使うと必要な処理を別の箇所に記述しなければなりませんでしたし、completionHandler を使うと コードが nest を繰り返し 処理の流れを追いづらくなってしまうという問題がありました。

# 以下の記事では completionHandler を Combine 経由で利用するケースを説明しています。当時は、async や await がまだありませんでした。
[Photos][Combine] requestAuthorization を Combine と組み合わせて使う

HealthKit を使う準備

HealthKit を使うためのプロジェクト設定

プロジェクト作成後以下の設定を行う必要があります。

  • Capability に HealthKit を追加
  • Info.plist に "Privacy - Health Update Usage Description" を追加し、理由を記述する
    自分のアプリが HealthKit のデータを読むケースで必要となります(自アプリが記録したデータは対象外です)
  • Info.plist に "Privacy - Health Share Usage Description" を追加し、理由を記述する
    自分のアプリが HealthKit へデータを保存し、そのデータを読み出すケースで必要となります

# Xcode13 以降では、Info.plist が見えなくなっていて直接編集できません。
[Xcode] Xcode13 で作成した新規プロジェクトに Info.plist がない件

MEMO
ここでは、特に async/await 対応に起因する変更はありません。

HealthKit 初期化とアクセス要求

HealthKit を使う時には、以下の点に注意しないといけません。

  • 機種によっては、HealthKit が使えない(iPad 等)
  • アプリが HealthKit データにアクセスしてよいかの許諾をユーザーに求めることが必要
  • ユーザーが、許諾しないデータを取得しようとしてもエラーではなくデータが存在しないとなる
  • ユーザー許諾をリクエストしないままデータの単位系を取得しようとすると エラーになる
    ドキュメントでは許諾されていないデータ単位系は nil が返るとなっていますが、iOS 15 beta では取得できました。

基本的に 許諾を得ようとしないことはあり得ません。(許諾なしにアクセスすると アプリはクラッシュしますし、レビューでもリジェクトされるはずです)

ただし、ユーザーが許諾しないことは考えられますし、当初許諾しても 後から 設定アプリで 許諾を取り消すことも可能ですので、許諾されていないときにどうするかを考える必要はあります。

以下のコードは、HealthKit 中の 体重(.bodyMass) と体脂肪率(.bodyFatPercentage) を読み書きすることを wrap するクラスをイメージしています。


//
//  HealthKitDataStore.swift
//
//  Created by : Tomoaki Yagishita on 2021/08/31
//  © 2021  SmallDeskSoftware
//

import Foundation
import HealthKit

class HKDataStore {
    // (1)
    var healthStore: HKHealthStore!

    // (2)
    // types app will handle
    let weightType = HKQuantityType.quantityType(forIdentifier: .bodyMass)!
    let ratioType = HKQuantityType.quantityType(forIdentifier: .bodyFatPercentage)!

    // (3)
    init?() {
        // (4)
        if !HKHealthStore.isHealthDataAvailable() {
            return nil
        }

        self.healthStore = HKHealthStore()
        
        // (5)
        Task {
            do {
                // (6)
                try await self.healthStore.requestAuthorization(toShare: [weightType, ratioType], read: [])
            } catch {
                print("error")
            }
        }
    }
}
コード解説
  1. アプリから使用する HKHealthStore のインスタンスを保持します
  2. アプリが使用するタイプは、コード中で何回か参照されるのでインスタンス変数にして持っています
  3. failable initializer を使っています。HealthKit が使えない時には、init が nil を返します
  4. isHealthDataAvailable を使用して、デバイスで HealthKit が使えるかを判断しています
  5. async コード中から sync を呼ぶために、Task を使用します(async な requestAuthorization を呼ぶために必要です)
  6. アクセス許諾をリクエストします

HealthKit の requestAuthorization

Apple のドキュメントみると、iOS15 以降向けに新しい API が用意されています。Apple のドキュメントは、こちら


func requestAuthorization(toShare typesToShare: Set, read typesToRead: Set) async throws

話題(?) の async が付与されている API です。

参考までに CompletionHandler 版の API は以下です。


func requestAuthorization(toShare typesToShare: Set?, 
                     read typesToRead: Set?, 
               completion: @escaping (Bool, Error?) -> Void)

相違点は、completion の代わりに、"async" が付与されている点と、"throws" も付与されている点です。

throws が追加されている理由

throws は、呼び出した処理内でエラーが発生した時に、エラーとして呼び出し元に戻るために追加されています。

CompletionHandler 版の API では、closure に 結果とエラー が渡されてきます。ですが、async 版では、エラーが渡されることはありません。そのエラーは、async 版では、例外として投げられてきます。

なお、この設計は、HealthKit 特有の設計ではなく completionHandler, delegate 的な API に対応する 関数の async 版で一般的に採用されているルールです。

async

# 本題です。

async というキーワードも追加されています。

async は、その関数が処理中に suspend されるかもしれないことを意味します。

CompletionHandlerがその記述タイミングで実行されないかもしれないことと同様に、async が付与された関数をコールしても、直後に実行されないかもしれないという意味です。(関数内で suspend される可能性もあります)

async 版 requestAuthorization

async 版の requesetAuthorization は、throws と async の両方がついていますので、以下のような振る舞いをすることがわかります。なお、関数返り値も持たないことは、CompletionHandler 版と同様です。

  • throws がついているので、エラーは 例外として投げられてくる
  • async がついているので、関数コール直後に実行されないかもしれない
  • 関数返り値がない ( CompletionHandler 版と同様)

以上で、HealthKit のセットアップは終了です。インスタンス化した HKHealthStore にアクセスしていくことになります。

まとめ:HealthKit のセットアップ async/await 対応板

HealthKit のセットアップ async/await 対応板
  • HealthKit のセットアップでは、async/await 対応で大きな変化はない
  • async/await 対応した requestAuthorization を呼ぶときは、Task を使う

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です