Core Data / SwiftData の iCloud 同期が実行されていることを可視化する

iOS / macOS アプリのデータベースに Core Data や SwiftData を使うと、 iCloud 同期を無料かつ簡単に使うことができます。ちょっとやってみるレベルの個人開発だと絶対に自分でサーバーを管理したくないので、複数デバイスからまともに使えるアプリを作ろうと思うと iCloud 同期はリーズナブルな選択肢だと思います。

ただし、アプリから iCloud 同期を実行したり停止したりする API はないので、同期の実行は OS のご都合のよいタイミングでよろしくお願いしますという感じになっています。これは iCloud 同期を利用するアプリの開発者としても利用者としてもかなり気になる点で、 ある端末でデータを更新したのにもう一方の端末には反映されていなくて、それぞれの端末でアプリを操作してみたり再起動してみたりしても全然同期されないということが起こったりします。

同期タイミングをコントロールできないこと自体は iCloud の仕様なのでどうにかするのは難しいですが、少なくとも同期が走っているのかどうかを知ることができればうれしそうです。そのために、 iCloud 同期のイベントが NSPersistentCloudKitContainer.eventChangedNotification の中に NSPersistentCloudKitContainer.Event として流れてくることが利用できます。

NotificationCenter.default
            .publisher(for: NSPersistentCloudKitContainer.eventChangedNotification)
            .receive(on: DispatchQueue.main)
            .compactMap { notification in
                notification.userInfo?[
                    NSPersistentCloudKitContainer.eventNotificationUserInfoKey
                ] as? NSPersistentCloudKitContainer.Event
            }
            .sink { event in
                // event が iCloud 同期に関わる何らかのイベントを表している
            }

この Eventtype プロパティは enum になっていて、これを見ることでイベントの種類がわかります。

  • .setup : iCloud コンテナのセットアップ
  • .import : 実行中のアプリのデータ更新を iCloud に反映する
  • .export : iCloud 上のデータ更新を実行中のアプリに反映する

Event はイベントの開始時と完了時に流れてきて、開始時は endDate プロパティに nil が、完了時はイベントの完了時刻が入っています。また、 identifier プロパティはイベントの id を表し、同一イベントの開始と完了の Event は同一の identifier を持ちます。

例えばアプリデータを更新したタイミングではエクスポートが走る必要があるため、

  • エクスポート開始時に type.exportendDatenilEvent が流れてくる
  • エクスポートが完了したら同じ identifiertype.exportendDate に完了時刻が入った Event が流れてくる

ということになります。つまり、 endDatenil のイベントが流れてきたら、同じ identifierendDate に時刻が入ったイベントが流れてくるまでは iCloud 同期が実行中であるということがわかります。また、 error プロパティを見ることで、イベントにおいてエラーが発生したかどうかも確認することができます。

以上を利用することで、 iCloud 同期の状況をある程度はアプリ上で可視化することができます。そこまでアプリのエンドユーザに見せるべきなのかは諸説ありそうですが、まあ見せたかったら見せてもいいんじゃないでしょうか。一度きりの人生、好きなように生きていこう。

最近趣味で iCloud 同期を利用した RSS リーダーを作っていて、同期中はそうとわかるようにローディングアイコンを出すようにしています。エントリを開くたびに右上の iCloud アイコンがローディング状態になるのがわかるでしょうか。エントリの既読状態を iCloud に反映するために同期が走っていることを表しています。これを見るたびにちゃんと同期されてる〜という癒しが得られていて、ストレス社会の中の一服の清涼剤という感じがします。

加えて、その iCloud アイコンをタップ時に同期イベントの履歴が見られる画面を表示しています。どのレコードが同期されたかなどの詳しい情報はもちろんわからないのですが、インポートとエクスポートがそれぞれいつ実行されたのかと、成功したか失敗したかがわかります。

上記の表示は iCloud 同期の状況を管理するための @Observable によって行っています。参考までにコードを載せておきます。

gist.github.com

参考

developer.apple.com

alexanderlogan.co.uk