動画を1本添付しただけでアプリが落ちるようになった、長い夜の話
めぐる日記に写真だけでなく動画も添付できるようにした。ついでに、日記のデータをiCloudにワンタップで保存・復元できる機能も作った。ここまでは順調だったんだけど、動画を何本か添付した状態で「バックアップを書き出す」を押した瞬間、アプリが問答無用で強制終了するようになった。
落ちる時はいつも同じ、SIGKILL
実機のログを取って確認すると、クラッシュの原因は毎回同じ「signal 9」、いわゆるOOM(メモリ不足)による強制終了だった。まだ動画を数本入れただけなのに落ちる、というのが引っかかった。
当時のバックアップの仕組みは、日記の本文もタグも写真・動画の実体も、全部を1つの巨大なテキスト(JSON)にまとめて書き出す方式だった。写真や動画のバイト列は、テキストとして扱えるようbase64という形式に変換して埋め込んでいたんだけど、この変換で元のサイズの1.3倍くらいに膨らむ上に、それを「1つの巨大な文字列」としてメモリに全部載せてから書き出す作りになっていた。動画は写真と違って数十MB単位になることも多く、この「全部を一度にメモリに載せる」設計がそのまま限界を迎えていた。
最初は「危なそうなサイズになったら事前に警告してテキストだけの保存に切り替える」という応急処置を入れたんだけど、動画を数本入れただけでその警告が出るようになってしまい、これでは機能として意味がないという話になった。ここで方針を変えて、根本的に作り直すことにした。
作り直し:ファイルはファイルのまま送る
新しい方式では、バックアップの中に写真・動画の実体をまったく含めないことにした。日記の本文やタグなど軽いデータだけを1つの小さなファイルにまとめ、写真・動画は「ファイルのまま」別経路で転送する。iCloud保存にはCloudKitが用意している、ファイルを直接転送する仕組みを使い、書き出し用のファイルには、複数のファイルをひとまとめにできる「パッケージ」形式を採用した。どちらも、base64変換や「全部を一度にメモリに載せる」という工程が発生しない。
この作り直しのあと、実際に400MB超のデータでバックアップを試しても、クラッシュしなくなったのを確認できた。
直したはずなのに、また別の壁
ファイル転送に切り替えたことで、今度は「iCloud上にある自分のバックアップの一覧を取得する」処理を書く必要が出てきた。何の変哲もない検索のつもりで書いたら、「recordNameというフィールドは検索可能に設定されていない」という趣旨のエラーで弾かれてしまった。
調べてみると、CloudKitでは検索(クエリ)に使うフィールドを、事前に管理用の画面で「検索可能」に設定しておく必要があるという仕様だった。アプリのコード側だけではどうにもならない制約だったので、方針を変えて「検索は一切しない」設計に切り替えた。具体的には、バックアップの管理データの中に「今存在するファイルの名前一覧」を持たせておき、各ファイルはその名前から直接IDを組み立てて1件ずつ取りに行く、という力技だけど確実な方法にした。
ファイル名が壊れて出てくる
もう一つ見つかったのが、iCloudからダウンロードしてきたファイルの、端末上でのキャッシュ名が元のファイル名を保っていない、という問題だった。拡張子のつもりで取り出した部分が、実際には全然関係のない長いランダムな文字列になっていることがあった。
これに気づかず動画の拡張子をそこから決めていたせいで、復元したはずの動画が正しく再生できないことがあった。修正は、ダウンロード後のキャッシュ名は信用せず、バックアップを作った時点で記録しておいた「本来のファイル名」から拡張子を取り直す、というものだった。
一番厄介だったのは、消えていなかったデータ
ここまでの修正を終えて一通り動くようになったあと、奇妙な現象に遭遇した。ホーム画面では写真・動画が灰色のまま表示されないのに、同じ日の編集画面を開くと正常に表示される。しかも、その状態で日記を手動で削除しても、同じ日付の「別のデータ」がまた出てくる、ということが何度も起きた。
これは、日記のデータそのものが重複して存在していることを示していた。検証を重ねてわかったのは、日記データの保存先(ウィジェットと共有するための領域)が、アプリを一度アンインストールしても確実には消えてくれないケースがある、ということだった。一方、写真・動画の実体ファイルは別の場所に保存していて、そちらはアンインストールで確実に消える。結果として「参照だけ残っているのに、実体は消えている」というズレが起き、それが積み重なって重複データや灰色のサムネイルの原因になっていた。
これは開発中のテストに限った話ではなく、実際に使っている人がアプリを一度削除して入れ直した場合にも起こりうる。しかも、単純に「iCloudから復元する」を押せば、その時点にある1件のデータには正しく上書きされて直ることも確認できた。つまり本当の問題は、直し方を知らないまま「原因不明の灰色」を見てしまうこと自体だった。そこで、写真・動画のファイルが見つからない状態を検知したら、「復元する」への案内を画面に出す仕組みを追加した。
持ち帰ったこと
- 「軽いデータ」と「大きな実体(写真・動画などのファイル)」は、最初から別の経路で扱うように設計した方が、あとで詰まりにくい
- クラウド側の一覧取得(検索)は、管理コンソールでの事前設定が必要になることがある。検索に頼らず、決まったルールでIDを組み立てて直接取りに行く方法も持っておくと安全
- ダウンロードしてきたファイルの「その場のファイル名」は信用せず、必要な情報は自分で持っている元データから取り直す
- 「アンインストールすれば必ず全部消える」という前提は、共有ストレージが絡む場合は成立しないことがある。直せる不具合と直せない不具合を分けたうえで、直せない方は「気づかせて、案内する」設計で受け止める