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

EventKit

async/await を使った EventKit への requestAuthorization を説明します。

環境&対象

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

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

EventKit

EventKit は、iOS/macOS でのカレンダーイベントや リマインダーアイテムを管理している Framework です。

カレンダーからイベント情報を取得したり、自分で作成したアイテムをリマインダー に追加するときに使うことになります。

以下の記事では、HealthKit について説明していますが、おおよそ同じような使い方です。
[iOS][HealthKit] HealthKit のセットアップ async/await 対応板

async/await 対応した EventKit

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

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

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

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

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

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

await を使った呼び出しに変更することで、機能的な相違はありません。ですが、コード的には以下のように改良することができます。

  • completionHandler による コードのネスト数増加 を防止
  • delegate に記述することでの 処理の流れを確認するときの難しさを軽減

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

EventKit を使う準備

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

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

  • Info.plist に "Privacy - Calendars Usage Description" を追加し、理由を記述する
    自分のアプリが カレンダーイベント のデータを参照・編集するケースで必要となります
  • Info.plist に "Privacy - Reminders Usage Description" を追加し、理由を記述する
    自分のアプリが Reminder アプリのデータを参照・編集するケースで必要となります
  • Info.plist に "Privacy - Contacts Usage Description" を追加し、理由を記述する
    自分のアプリが アドレスブックのデータを参照・編集するケースで必要となります EventKitUI を使うときに使うかもしれないと記述されているので、EventKitUI を使わないケースでは不要です。
  • (macOS のみ必要) AppSandbox を追加 して、該当の App Data にチェックを入れる

以下は、Info.plist の編集例と AppSandbox 設定例です。

追加したプロパティ
追加したプロパティ
Sandbox
Sandbox

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

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

EventKit 初期化とアクセス要求

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

  • アプリが EventKit に関連するデータにアクセスしてよいかの許諾をユーザーに求めることが必要
  • ユーザーからの許諾を得ないままアクセスすると クラッシュ する

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

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

以下のコードは、EventKit 経由で カレンダーイベントを読み書きすることを wrap する EventKitStore クラスを作ることを例にしています。

async/await 以前


class EventKitStore: ObservableObject {
    let store: EKEventStore
    var accessToEvent: Bool = false

    init() {
        // (1)
        store = EKEventStore()

        // (2)
        if EKEventStore.authorizationStatus(for: .event) != .authorized {
            // (3)
            store.requestAccess(to: .event) { (result, error) in
                if let error = error {
                    print("\(error.localizedDescription)")
                } else {
                    self.accessToEvent = result
                    // (4-1)
                    if accessToEvent {
                        // do preprocess
                    }
                }
            }
        } else {
            accessToEvent = true
        }
        // (4-2)
		 if accessToEvent {
          // do preprocess
        }
    }
コード解説
  1. アプリから使用する EKStoreKit のインスタンスを保持します
  2. カレンダーアイテムへのアクセスが許諾されているか確認します
  3. 許諾されていなければ許諾を求めます
  4. 許諾を確認して、EventKit のデータを使用した前処理等を行います。(処理する前に、許諾状態を確認することが必要です、許諾がないとデータが取得できません エラーではなくデータが存在しないように見えます)

async/await 対応


class EventKitStore: ObservableObject {
    let store: EKEventStore
    var accessToEvent: Bool = false

    init() {
        // (1)
        store = EKEventStore()
        
        // (2)
        if EKEventStore.authorizationStatus(for: .event) != .authorized {
            // (3)
            Task {
                // (4)
                do {
                    // (5)
                    self.accessToEvent = try await store.requestAccess(to: .event)
                } catch {
                    print("\(error.localizedDescription)")
                }
                // (6-1)
                if accessToEvent {
                    // do preprocess
                }
           }
        } else {
            self.accessToEvent = true
        }
        // (6-2)
        if accessToEvent {
          // do preprocess
        }
    }
}
コード解説
  1. アプリから使用する EKStoreKit のインスタンスを保持する点は同じです
  2. カレンダーアイテムへのアクセスが許諾されているか確認する方法も同じです(このメソッドは、sync で動作します)
  3. async/awaiyt 対応したメソッドを sync メソッドから呼ぶときには、Task を使用します
  4. async な requestAccess は、throw によってエラーを出力するので、do {} catch {} でのエラー処理を追加します
  5. requestAccess で許諾をリクエストすることは同じですが、結果は、関数返り値として返ります (返り値が戻るまで このスレッドは、suspend されます)
  6. EventKit のデータを使った前処理のタイミングには、大きな変更はありません

もう少し EventKit の requestAuthorization を説明します。

EventKit の requestAuthorization

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


func requestAccess(to entityType: EKEntityType) async throws -> Bool

async が付与されている API です。

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


func requestAccess(to entityType: EKEntityType, 
        completion: @escaping EKEventStoreRequestAccessCompletionHandler)

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

throws が追加されている理由

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

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

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

async

async というキーワードが追加されている API を確認していく前に、async 自体の意味を確認します。

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

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

async 版 requestAuthorization

async 版の requesetAuthorization は、throws と async の両方がついていますので、以下のような振る舞いをすることがわかります。

  • throws がついているので、エラーは 例外として投げられてくる
  • async がついているので、関数コール直後に実行されないかもしれない
MEMO
async 版の requestAuthorization の返り値は、ユーザーが許諾したかどうかで true/false が返ってくるようです。(ドキュメントには記載がありません)

EKAuthorizationStatus は、authorized, denied 以外にも、notDetermined, retricted が持てるのですが、requestAuthorization からは、このような値は返されません。

ちなみに、ユーザーに表示される UI でも、”アクセスを許しますか?” -> "Yes" "No" 的な UI なので、現実的には、authorized と denied のステータスのみが 存在するのかもしれません。

以上で、EventKit のセットアップは終了です。許諾が取れたならば、インスタンス化した EKEventStore にアクセスして 必要なデータを取得したり データを更新していくことになります。

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

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

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

コメントを残す

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