iOSアプリ「加賀ゆびぬき 刺し模様シミュレータ」をObjective-CからSwiftへ書き換えた記録です。
かなり雑な備忘録になりました。
XCode7 / Swift 2.0
まずはモデル層を移植することにした
シリアライズ
NSCodingのシリアライズは優秀だけど、それを引き継ぐにはNSObjectを継承したクラスを定義する必要があります。Swift実装で完結させるため、別の方法でシリアライズすることにしました。
SwiftのObjectからNSDictionaryを生成し、それをファイルへ保存することにしました。
Swiftの世界でJsonを扱う方法を参考にしています。
List 1
// Objective-C
@interface someVO : NSObject<NSCoding>
@end
List 2
// Swift
protocol Serializable {
init(withDictionary dictionary: NSDictionary)
func toDictionary() -> NSDictionary
}
class someVO : Serializable {
// (略)
}
マイグレーション
VOのクラスを変更するので、過去バージョンでシリアライズしたデータを復元する機構が必要になります。
問題になるのは、過去バージョンのデータを読込む方法です。
Objective-Cのコードで書かれた部分と、Swiftコードで書かれた部分は名前空間が別になるため、過去バージョンのデータを読込む処理は、過去バージョンのコードをそのまま使うことにしました。
同じプロジェクト内に、上記のList 1, 2のコードを両方含めるのです。
List 1のコードを使用して、過去バージョンのデータを読込む処理をObjective-C で記述します。読み込み処理の結果は、VOではなく、NSDictionaryで返します。これをSwift側から呼び出すことで、過去のVOをSwift側に持ち込むことなく済みました。
テスト
モデル層を移植すると同時に、そのテストもSwiftに書き換えることになりました。テストフレームワークとしてQuickを使っていたにも関らず、テストをObjective-Cで書いていたからです。
おかげで、副産物としてテストのリファクタリングができ、テストケースの見直しができました。(バグもみつかりました)
Viewの移植にとりかかる
static method は、Swiftでも static method だった
NSTimerが発火しない問題が発生した。
アホなはなしだが、同じ引数をとるコンストラクタに浮気したら、仕事しなかった。
// Objective-C
self.animationTimer =
[NSTimer scheduledTimerWithTimeInterval:0.5f
target:self
selector:@selector(timerFired:)
userInfo:nil
repeats:YES];
// Swift
// めっちゃ似てるけど、これは発火しない
// self.animationTimer = NSTimer(timeInterval: 0.5, target: self, selector: "timerFired:", userInfo: nil, repeats: true)
// こっちは発火した
self.animationTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self,
selector: "timerFired:", userInfo: nil, repeats: true)
振返り
だいぶ時間がたってしまったので、ざっくり振り返る。
- 旧アプリのswiftと、新アプリのswiftのバージョンが異なり、XCodeを切り替えてプロジェクトを開かなければならなかったのは、めんどうだった
- あえて古いバージョンのswiftで書く必要性はないのだが、新旧のアプリを同じシミュレータで試すことができず、動作確認はわりと手間だった
- それなりに画面数があるなら、UITestを準備してもよかったかも
- よくテストしたつもりだったけど、ひとつだけデータのマイグレーションに失敗することがあって、緊急レビューを依頼する事態になった
- Swiftは想定外の状態になったときに「積極的に落とす」ということを失念していた
- カバーが必要な場面は、想定して実装する必要があった
- 詳しくはこちら → 個人アプリで起動できない不具合を出した話