機種変更をしたユーザーから「アプリを開いたら全部最初からになっていた」とレビューが付くとき、原因のほとんどは iCloud バックアップ周りの設定漏れです。Rork で素直に作ると AsyncStorage や Keychain に保存したデータが復元時に持ち越されないことが多く、せっかくのリピーターを毎回失っていくことになります。
私自身、過去に3つのアプリで同じ問題に当たりました。デバッグでは何も起きず、TestFlight でも気付かず、本番のレビュー欄で初めて発覚するタイプのバグです。出荷前に必ず潰しておきたい挙動なので、ここでは「Rork アプリのデータがどこに保存されていて、復元時にどう振る舞うのか」を整理し、データを失わずに引き継ぐための設定方法を順を追って説明していきます。
なぜ Rork アプリは iCloud 復元後にデータが消えるのか
iOS のデータ保存先は、iCloud バックアップに含まれるかどうかが場所ごとに異なります。Rork(React Native + Expo)でよく使う保存先を整理すると、以下のように分かれます。
- AsyncStorage: 内部的には
Library/Application Support/RCTAsyncLocalStorage/以下のファイルです。iCloud バックアップに含まれます が、ライブラリ次第で「除外フラグ」が付いていることがあるので注意が必要です - Documents フォルダ: ユーザーが作成したファイルを置く場所です。デフォルトで iCloud バックアップに含まれます
- Library/Caches: キャッシュ用です。iCloud バックアップに含まれません(システムが任意で削除する可能性もあります)
- tmp/: 一時ファイルです。iCloud バックアップに含まれません
- Keychain: 暗号化された資格情報置き場です。
kSecAttrAccessibleの設定次第で iCloud Keychain 経由で復元先に持ち越されます
データが消える典型パターンは、おおむね次の3つに集約されます。
- ユーザー作成データを
Library/Caches/に保存していた — 大きな画像や録音データを Caches に置くと、復元時にゼロから生成し直しになります - AsyncStorage の保存先がいつの間にかバックアップ除外フラグ付きになっていた — 一部のライブラリが内部で自動的にフラグを付けます
- Keychain の
kSecAttrAccessibleがThisDeviceOnly系になっていた — ログイントークンが復元時に持ち越されません
それぞれの直し方を順に見ていきます。
まずは保存先を可視化する
自分のアプリがどこに何を保存しているかを把握しないことには、話が始まりません。Rork のアプリで保存先を実機確認するためのデバッグスニペットを置いておきます。
import * as FileSystem from 'expo-file-system';
import AsyncStorage from '@react-native-async-storage/async-storage';
// アプリ起動直後に1回だけ呼んで確認用ログを出す
export async function debugStorageLocations() {
console.log('documentDirectory:', FileSystem.documentDirectory);
console.log('cacheDirectory:', FileSystem.cacheDirectory);
console.log('bundleDirectory:', FileSystem.bundleDirectory);
const keys = await AsyncStorage.getAllKeys();
console.log('AsyncStorage keys count:', keys.length);
console.log('AsyncStorage keys:', keys);
}iOS 実機で実行すると、以下のような出力が得られるはずです。
documentDirectory: file:///var/mobile/Containers/Data/Application/<UUID>/Documents/
cacheDirectory: file:///var/mobile/Containers/Data/Application/<UUID>/Library/Caches/
AsyncStorage keys count: 12
ここで Library/Caches/ 配下にユーザーが「自分が作った」と認識しているデータ(撮影した画像、ダウンロードした音声、書いたメモ等)を保存していたら、それは設計ミスです。Documents もしくは Library/Application Support/ に移動させてください。
判断基準としては、ユーザーが自分のものとして認識するデータは Documents、アプリ内部のマスターデータや設定は Application Support、再生成可能なキャッシュだけ Caches、と分けるのが基本になります。
ファイルごとに「バックアップに含めるか」を明示する
iOS にはファイル単位で「これはバックアップに含めない」と指定する仕組みがあります。これを忘れると、巨大な動画ファイルを Documents に置いただけで iCloud 容量を圧迫してしまい、ユーザーから「容量を食われている」とクレームが来ることがあります。
逆に、本当はユーザーデータなのに Excluded from backup フラグが付いていれば、それは復元時に消えてしまいます。Expo / React Native では以下のように制御できます。
import * as FileSystem from 'expo-file-system';
// バックアップから除外(再ダウンロード可能なキャッシュ画像など)
await FileSystem.setExcludeFromBackupAsync(
`${FileSystem.documentDirectory}cached_thumbnails/photo_001.jpg`
);
// バックアップに含める(ユーザーが手書きしたメモなど)
// → デフォルトで含まれるため、明示的にフラグを外す API は基本不要
// 過去に setExcludeFromBackupAsync を呼んでしまった場合は、
// 別のファイルパスにコピーして作り直す期待する動作としては、setExcludeFromBackupAsync を呼んだファイルは iCloud バックアップから除外され、復元先のデバイスには戻りません。呼ばなければデフォルトでバックアップ対象です。
私が一番ハマったのは、自前でサムネイル画像を Documents 配下に保存していた箇所です。元画像は外部から再取得可能だったので本来 Caches で十分だったのですが、Excluded from backup フラグも付け忘れていたために iCloud 容量を不必要に食っていました。「ユーザー本人が作ったか、外部から取得して再現できるか」で必ず仕分けてください。
Keychain の kSecAttrAccessible を機種変更に強い設定にする
ログイントークンを Keychain に保存している場合、kSecAttrAccessible の設定によって機種変更時の挙動が大きく変わります。expo-secure-store を使っているケースで例を示します。
import * as SecureStore from 'expo-secure-store';
// ❌ ダメな例: ThisDeviceOnly 系は機種変更時に持ち越されない
await SecureStore.setItemAsync('auth_token', token, {
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
// ✅ 良い例: 機種変更で iCloud Keychain 経由で復元される
await SecureStore.setItemAsync('auth_token', token, {
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
});期待する挙動としては、AFTER_FIRST_UNLOCK で保存しておけば、機種変更後の新しい端末で初回ロック解除した直後から、同じ Apple ID で iCloud Keychain が同期されているユーザーは引き続きログイン状態を維持できます。
ただし、トークンの種類によっては「端末固有で、持ち越したくない」ケースもあります。たとえば指紋認証や FaceID で守っている決済用トークンは、安全性の観点から WHEN_PASSCODE_SET_THIS_DEVICE_ONLY のほうが望ましい場合もあります。「ユーザーに新しい端末で改めてログインしてもらうか、黙って引き継ぐか」をプロダクト側の方針として先に決めてから設定するのがおすすめです。 Keychain の詳細な使い分けは Rork × Keychain で機密情報を安全に保存する完全ガイド でも触れていますので、合わせて参照してみてください。
出荷前テスト — iCloud 復元シナリオを実機で再現する
設定を直したら、必ず本番に出す前に「実際に復元してみて」確認します。シミュレータでは正確に再現できないので、実機・Apple ID・少しの待ち時間が必要です。
- テスト端末A で iCloud バックアップを作成
- 設定 → Apple ID → iCloud → iCloud バックアップ → 今すぐバックアップを作成
- バックアップが「最後のバックアップ: 今」になるまで待つ
- テスト端末B(または同じ端末を初期化)に復元
- 設定 → 一般 → 転送または iPhone をリセット → すべてのコンテンツと設定を消去
- 初期セットアップで「iCloud バックアップから復元」を選び、1 で作ったバックアップを指定
- アプリを開いて、保存していたデータが残っているか目視確認
- ログイン状態が維持されているか
- ユーザーが作成したコンテンツが表示されているか
- ダウンロード済みファイルが復活しているか、あるいは「再ダウンロードを促す UI」が正しく出ているか
ここで何かが消えていれば、保存先・バックアップフラグ・Keychain のいずれかに問題があります。日付や時刻まわりも復元後に違和感が出やすい箇所なので、合わせて Rork で日付が日本時間にならない・9時間ずれる時の直し方 も読んでおくと安心です。
Android 側の挙動も合わせて確認する
Android にも似た仕組みがあります。react-native-async-storage はアプリのサンドボックスに書き込まれ、Android 6 以降は Auto Backup の対象に入ります。ただし AndroidManifest.xml で android:allowBackup="false" を設定していると同じく「復元後に空」になります。Rork で素のまま使っているなら基本は安全ですが、過去に「セキュリティ強化」を謳う記事から Manifest 設定をコピーした覚えがあれば、出荷前に該当箇所を確認しておくのがおすすめです。
本当に大きなユーザー生成ファイル(写真・動画など)については、そもそも iCloud や Google Drive 経由のバックアップで運ぶのは現実的ではありません。メタデータだけ AsyncStorage や SQLite に置き、バイナリは Supabase Storage や自前のバックエンドへ預けて、復元時には参照だけ戻して必要に応じて再ダウンロードさせる設計が、長期的には一番素直です。
全体を振り返って — 復元時の挙動はリリース前に必ず確認する
機種変更や端末初期化からの復元は、ユーザーが「このアプリを使い続けるか、ここで辞めるか」を決定する瞬間です。ここで全部消えると、どれだけ作り込んだアプリでも一発で星1レビューが付いてしまいます。
今日できる具体的な一歩として、まずは自分のアプリで debugStorageLocations を呼んで、何がどこに保存されているのかを書き出してみてください。「Caches に置いていてはいけないもの」が1つでも見つかれば、それを直すだけで復元シナリオの大半は改善します。Keychain の kSecAttrAccessible も、開発初期の値のまま忘れがちな設定なので、合わせて見直してみることをおすすめします。