すべての記事
エンジニアリング

iOSのカメラバックアップ:その仕組みを内側から

iOSでカメラロールをクラウドストレージにバックアップするのは、見た目より厄介な落とし穴が多いものです。アップロードの途中でOSにアプリを終了されても生き延びる仕組みを、私たちがどう作ったのかをご紹介します。

2026年4月15日 約8分
カメラロールのファイルを暗号化クラウドストレージへ同期する、iPhoneの写真バックアップの仕組みのイラスト。

紙の上では「写真をバックアップする」は一行の機能です。しかし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はキュー投入時に事前署名するので、ワーカーが転送の途中で認証トークンを必要とすることはありません。
バックグラウンドURLSessionはシミュレータではテストできません — 実機が必要です。私たちは これを痛い思いをして学びました。

残りを片づけるBGTaskScheduler

プロジェクトには2つのタスク識別子が登録されています —us.virtualdrive.sync us.virtualdrive.backup。iOSは、キューに残ったものを片づけるため、ときどき アプリをバックグラウンドで起こします — たいていは夜間、電源につながれてWi-Fiに接続 されているときです。

ユーザーの制御:尊重を、その上でデフォルトを

カメラバックアップはオプトインです。有効にしたときのデフォルトは次のとおりです。

  • ユーザーが明示的にモバイル通信を有効にしない限り、Wi-Fiのみ。
  • バッテリーが20%未満で、端末が電源につながれていないときはスキップ。
  • 設定 → 同期 から一時停止可能(写真バナーが一時停止状態をリアルタイムに反映)。

「これはバッテリーを消耗させるべきか?」への唯一許される答えは「絶対にノー」だと 私たちは考えています。だから、一時停止する側に倒します。

これで得られたもの

ユーザーがアプリを強制終了しても完了し、アップロードを重複させず、バッテリーを 激減させないバックアップの仕組み。写真タブは、残り時間の目安つきでライブの進捗を 表示します。新しい端末は、すでにバックアップ済みのものを再発見し、きれいに再開します。

華やかなエンジニアリングではありませんが、ちゃんと世に出せる類のものです。