「iCloud が新しければ取り込む」の関数が、何も比べていなかった:応募の記録が 20 件から 1 件になりうる
トレカダッシュ 開発の記録 / 2026年8月
iOS アプリ「トレカダッシュ」には、利用者が自分で書きためる応募の記録があります。どの抽選に応募したか、結果はどうだったか。手で積み上げた個人の記録なので、失うと取り返せません。
2026 年 8 月 23 日、まだ精読していなかったファイル(1,076 行)を読んで、この記録が、丸ごと消えうる経路を見つけました。きっかけは、関数の名前を信じていたことでした。
この記事は、2026 年 8 月 23 日の作業の記録から書いています。
名前は「新しければ取り込む」、中身は「何も比べない」
iCloud(Apple のクラウド)と同期する関数の名前は、loadFromICloudIfNewer、つまり「iCloud が新しければ、読み込む」です。ところが、中身は、新しいかどうかを一切見ておらず、iCloud に変更があると、その内容で、丸ごと置き換えていました。
これで、何が起きうるでしょうか。
- 端末 A に、応募の記録が 20 件ある(iCloud にも 20 件)。
- 端末 B を、新しくインストールする。iCloud の最初の同期は、非同期なので、起動した直後は、記録が空のことがある。
- 端末 B で、1 件、記録を追加する。保存の処理が、[1 件] を iCloud へ上書きする。
- 端末 A が、変更の通知を受けて、20 件から 1 件に置き換わる(19 件が消える)。
コードの中に、記録を書き出す、読み込む機能があったのは、「機種変更で、データが全部消えるのを防ぐため」と書かれていました。作った人も、この領域が壊れやすいことは、知っていたのです。
直し方:更新時刻を比べる、そして、空では上書きしない
- 更新時刻(
entriesUpdatedAt)を、端末と iCloud の両方に保存し、相手が新しいときだけ取り込む。関数の名前どおりの動作にする。 - 更新時刻が、まだ無い古いデータ同士でも、空の内容で上書きすることだけは、必ず防ぐ。
- 新しい端末で、最初に復元するときも、iCloud 側の時刻を引き継ぐ。
分かったこと
関数の名前が仕様を語っていても、中身がそうとは限らない。
- 名前を信じず、中身を読む。「IfNewer」と名乗りながら、比べていませんでした。
- 取り返せないデータの書き込みは、特に疑う。利用者が手で積み上げた記録は、失うと戻りません。
- 「空」は、危険な値。空の内容で上書きしないことを、最後の砦にします。
- 最初の同期は、非同期。起動した直後が空でも、データが無いとは限りません。
- 未点検のファイルは、読む。大きくて、古いファイルほど、見落としが残ります。
この話に出てくるもの
- トレカダッシュ — トレカの抽選・予約・先着の情報を集める iPhone アプリ
- Firebase Auth:アプリを完全に終了すると、匿名アカウントが量産された — 端末とアカウントの別の問題
- iOS アプリが固まって強制終了される — アプリ側の別の失敗
- 確認できなかったときに、通していた — 名前と中身が違った別の例
ほかの記録
- 「後から掃除するから大丈夫」が、入口の不具合を隠していた:商品が毎日 400 件そっくり作り直されていた — カタログからの商品登録が名前の完全一致でしか探さず、括弧・空白・全角の違いで毎日そっくりな商品を作り直し、この日だけで 437 件(410 件は記事なし)。統合の掃除が後から片付けていたため気づけなかった。
- Cloud Functions から月 4,300 円のサーバーへ移した:1 日 3,700 円の課金、EEA で全滅した Yahoo!、移行で踏んだ 5 つの罠 — 無料のお試しが終わると 1 日約 3,700 円(月約 10.6 万円)かかった。待ち時間にも課金される構造が原因で、コードをほぼ書き換えずにシンガポールの VPS(月約 4,300 円)へ移した。ヘルシンキでは Yahoo! が全滅、秘密の値は 5 本でなく 15 本だった。
- 広告が出ないと、実機チェックの 25 項目が止まった:開発者モードだけテスト広告を使い、賞品名を直書きしない — 配信前は広告の在庫が無く、実機チェックリスト 163 項目のうち 25 項目が確認できなかった。開発者モードのときだけ、Google のテスト用の広告枠に切り替えた。賞品名は 1 つの設定から読み、チェックリストにも直書きしない。
コメント
感想や、記事の間違いの指摘は、ここへ。書くには、GitHub のアカウントが要ります。
押すと、giscus(giscus.app)と GitHub に接続します。押さなければ、接続しません。詳しくはあつかいに書いています。