お久しぶりです。
最近理不尽なクラッシュを起こしてしまい悲しい気持ちになったので何が起こったのかとどうやって回避するかを書きます。
この事象は自分が業務で扱っているアプリをSwift3からSwift4へアップデートした時に起きた不具合になります。
事象
タイトルの通りクライアントアプリにローカル保存していたデータのdecode時にアプリがクラッシュします
より詳細に語ると
- Swift3の頃とSwift4記述は変わっていない
- Swift3のアプリverからSwift4対応版へのアップデートはクラッシュしない
- Objective-Cアプリ(で利用していたクラスのDecode)からSwift4対応版へのアップデート時にクラッシュする
このような事象です。
リリース直後なぜか過去のバージョンからのマイグレーション処理でクラッシュするログがでるようになったなと思ったのですが量も少なく(マイグレーション対象のクラスを利用していたアプリは2年以上前のバージョンだったため)、3.の情報がたまたま手に入ったためこの事象がSwift4の由来であると明るみに出ました。
原因
1.で述べている通り記述は変わっていない、ビルドも通る。この不思議な状態ですが勘のいい人はピンとくるかと思います。ランタイムです
Swift4からObjective-Cランタイムの扱いが変更されObjective-Cから呼び出されるメソッド(変数)もしくはクラスには@objcもしくは@objcMembersアノテーションが必要になりました。暗黙的な@objc推論の廃止ですね。
多々あるSwift4の記事で書かれているようによくあるパターンとして、#selectorのメソッドに@objcが必要になった等の内容で書かれています。
通常であればそもそもビルドが通らなくなるためすぐ気づくことが可能なのですが
NSKeyedUnarchiver.setClassメソッドを利用して以下のような記述をしていた場合クレイモア地雷になります。
NSKeyedUnarchiver.setClass(LegacyTestClass.self forClassName: "ObjecTestClass")
Swift3まではObjective-Cで書かれたObjecTestClassをデコードする際にLegacyTestClassとしてデコードするのですが、Swift4はLegacyTestClass.selfを解釈できずクラッシュします。
どうやらSwift内部的にObjectice-Cから参照しようとしているらしくLegacyTestClassを参照しようとしますが@objc推論が効かないため名前解釈できずクラッシュします
NSInvalidArgumentexception: -[AppName.LegacyTestClass initWithCoder:]: unrecognized selector sent to instance 0x1xxxxx
対応
素直にアノテーションをつけます
@objcMembers
class LegacyTestClass {}
これだけで解決します
/dev/null
ビルドエラーにしろよ!!!!こんなのリリース前にわからないよ!!!どうやってテストするんだ!!