この投稿は Sansan Advent Calendar 2015 の 7 日目の記事です。
突然のクラッシュ
とあるバージョンをリリース後に、突如下記の様なクラッシュがクラッシュレポートに出現。さらにリリースから数日後、このクラッシュが怒涛の如く増加した。
Fatal Exception: RLMException
Migration is required for object type 'XXXXX' due to the following errors: - Property types for 'XXXXX' property do not match. Old type 'float', new type 'double'. - Property types for 'XXXXX' property do not match. Old type 'float', new type 'double'.
どういう例外なのか?
Realm
ではrealmWithConfiguration
でrealmオブジェクトを取得する際に、RLMRealmConfigurationで指定したpathに保存されているRealmファイルのスキーマと、現在のモデルクラスから生成したスキーマを比較し、もしもマイグレーションが必要だった場合はRLMExceptionを発生させます。
つまり、もしもモデルクラスのプロパティをfloat
からdouble
に変更したにもかかわらずマイグレーションを実施していなかったならば、上記の例外が発生するのは自然なことです。
しかしモデルクラスの型は変えていなかった
とういうか、上記の例外でマイグレーションせよと言われてるプロパティは、flaot
でもdouble
でもなく、CGFloat
で保存していました。何も変更してないのになぜマイグレーションが必要なのか、、、?
CGFloatは端末によって型が違う
これは有名な話ですが、CGFloatの実体はCPUのアーキテクチャにより違う型となります。
CGFloatの定義をみると、こんな感じになっています。
64bitアーキテクチャではdouble, それ以外(32bit)ではfloatとして定義されています。
しかし、CPUアーキテクチャによって型が違うとはいえ、同じ型で保存されている限りは上記の様な例外は発生しないはずです。モデルクラスに修正は加えていないのに、どこで型が変わってしまったのでしょうか?
機種変更というトラップ
そう、同じ端末である限りはCGFloatの型が変わることはありません。しかし、機種変更とバックアップからデータを復元という合わせ技を行うと事情が変わります。
より具体的にいうと、32bitアーキテクチャな端末(iPhone5など)から64bitアーキテクチャな端末に機種変更をし、バックアップからデータを復元した場合です。
この場合、端末のバックアップに保存されているRealmファイルの型はfloatだが、最新のモデルクラスから生成したスキーマの型はdoubleという事態が起こります。最初に書いたエラーはこれが原因でした。
また、ちなみにリリースから数日後に突如エラーが頻発するようになった原因はその数日後というのが9月のiPhone6Sがちょうど発売するタイミングだったことでした。。。
上記の例外が発生した場合の対応方法
バージョンアップ時にプロパティの型をCGFloatではなくfloat
かdouble
、いずれかに指定してマイグレーションを実施することでエラーは収束しました。
というわけで
RealmのモデルクラスのプロパティにCGFloatを指定すると、上記の様な理由でクラッシュする危険性があるというお話でした。現在、Realmの最新のドキュメントにも
CGFloat型はプラットフォーム(CPUアーキテクチャ)によって実際の定義が変わるため、使用しないようにしてください。
と記述されており、基本的にはRealmにCGFloatを格納するのは止めておくべきでしょう。