紙の上では「写真をバックアップする」は一行の機能です。しかしiOSで実際にやると、 PhotoKit、バックグラウンドURLSession、BGTaskScheduler、コンテンツハッシュ、重複排除、 透かし(ウォーターマーク)、バッテリー方針に触れることになります。どれか一つでも 間違えれば、ユーザーは重複アップロード、取りこぼした写真、あるいは昼過ぎに切れる バッテリーのいずれかを味わうことになります。
私たちが行き着いた仕組みを、イベントが発火する順にご紹介します。
ピッカーではなく、PhotoKitのオブザーバー
ピッカー(PHPickerViewController)は単発のアップロードには最適ですが、 「新しいものをすべてバックアップ」には役立ちません。継続的なバックアップのために、 私たちはユーザーのライブラリ全体に対して PHPhotoLibraryChangeObserver を 登録します。カメラロールが変化するたび — 新しい写真、新しい動画、編集、削除 — その 差分がコールバックで届きます。
オブザーバーは、アプリが前面にある間、バックグラウンドキューで発火します。私たちが サスペンドされている間に撮られた写真に対応するため、アプリ起動時にも透かしを使って 差分を取得します。
透かし:たった一つの日付
アップロード済みの各アセットを個別に追跡する代わりに、UserDefaults に 一つのタイムスタンプ — 直近にアップロードした写真の creationDate — を 保存します。実行のたびに、creationDate > 透かし のアセットをPhotoKitに 問い合わせ、それらをキューに入れます。
これはアセットごとの状態管理よりも劇的にシンプルで、あらゆることを生き延びます — アプリの再インストール(サーバー側から透かしを再発見します)、ライブラリの整理、 iCloud写真の並べ替えさえも。
重複排除のためのコンテンツハッシュ
透かしだけでは不十分です。ユーザーがカメラバックアップを有効にし、無効にし、写真を 手動でアップロードし、その後バックアップを再び有効にすることがあります。重複排除が なければ、その写真は二重に入ります。
私たちは、PhotoKitからアセットのバイト列を読み取りながら、ストリーミングでSHA-256を 計算します。そのハッシュがすでにワークスペースに存在すれば、アップロードをスキップして 既存のファイルにリンクするだけです。2台の端末にある同一の写真は、同じ実体(blob)を 参照することになります。
バックグラウンドURLSession:アプリ終了を生き延びる
前面のURLSessionは、アプリがサスペンドされると死にます。喫茶店のWi-Fiで4GBの動画を 送っている最中には、それは致命的です。そこですべてのアップロードは、 URLSessionConfiguration.background(withIdentifier:) で構成した URLSession 上で実行します。
- システムが転送を引き継ぎます — ユーザーがアプリを終了しても継続します。
- 進捗はURLSessionのデリゲート経由で届き、それを写真バナーに配線して戻します。
- アップロードURLはキュー投入時に事前署名するので、ワーカーが転送の途中で認証トークンを必要とすることはありません。
残りを片づけるBGTaskScheduler
プロジェクトには2つのタスク識別子が登録されています —us.virtualdrive.sync と us.virtualdrive.backup。iOSは、キューに残ったものを片づけるため、ときどき アプリをバックグラウンドで起こします — たいていは夜間、電源につながれてWi-Fiに接続 されているときです。
ユーザーの制御:尊重を、その上でデフォルトを
カメラバックアップはオプトインです。有効にしたときのデフォルトは次のとおりです。
- ユーザーが明示的にモバイル通信を有効にしない限り、Wi-Fiのみ。
- バッテリーが20%未満で、端末が電源につながれていないときはスキップ。
- 設定 → 同期 から一時停止可能(写真バナーが一時停止状態をリアルタイムに反映)。
「これはバッテリーを消耗させるべきか?」への唯一許される答えは「絶対にノー」だと 私たちは考えています。だから、一時停止する側に倒します。
これで得られたもの
ユーザーがアプリを強制終了しても完了し、アップロードを重複させず、バッテリーを 激減させないバックアップの仕組み。写真タブは、残り時間の目安つきでライブの進捗を 表示します。新しい端末は、すでにバックアップ済みのものを再発見し、きれいに再開します。
華やかなエンジニアリングではありませんが、ちゃんと世に出せる類のものです。
