こんにちは。エムスリー エンジニアリンググループの藤原です(※ 技術顧問の藤原さんとは別人です)。 医師・薬剤師向けプラットフォーム「m3.com」のiOSアプリの開発をしています。 メインはiOSですが、Androidやサーバーサイドを担当したりと色々とやっています。
iOS版「m3.com」アプリは2014年ファーストリリースで、フルObjective-Cで書かれていましたが、 2019年2月のリリースをもって、フルSwift化が完了しました。 今回はこれまでコツコツと実施してきた、Swift化周りの取り組みについてお話します。
(wokoti [CC BY-SA 2.0], ウィキメディア・コモンズより)
モチベーション
私がm3.comアプリの開発にジョインしたのは2017年7月頃です。その頃には半分以上のコードはSwiftで書かれていました。
そのため、Swift化を始める際の議論に参加したわけではありません。
しかし、Appleが掲げているSwiftの特徴 – つまり「モダン」「安全のための設計」「高速・強力」 – をアプリ開発に携わるエンジニアの視点から解釈すると、以下のようなメリットが自ずと見えてきます。
品質向上
Swiftは安全なコーディングができるようになっています。オプショナル型はnilの可能性を考慮した設計を可能にし、ジェネリクスは静的な型チェックを可能にします。Objective-Cを使う開発者なら一度は経験したことがあるであろう、nil参照によるクラッシュ、不可能なタイプキャストから発生するクラッシュを未然に防ぐことができます。 アプリの品質の指標としてクラッシュフリー率を考えるなら、Swiftからもたらされるこれらの機能は有益です。開発効率アップ
強い型システムをもつSwiftは、その副産物として簡潔な記述ができるようになっています。そして記述量が減る分、開発効率をあげることができると言えるでしょう。 Objective-CからSwiftのフルリプレイスでステップ数が44%削減できたという話もあります*1。将来性
iOS用のライブラリの中にはObjective-Cでは扱えないものも増えてきています。Swiftのモダンであることを生かしたライブラリはその傾向が強いです。 世の中の流れにおいていかれないためにもSwiftを選択するべきかと思われます。
また、上記以外のSwift化する理由として 好きであること も挙げておきたいです。 開発における選択にはアーキテクチャの選定もありますが、iOS設計のバイブルとなった『iOS アプリ設計パターン入門*2』には、アーキテクチャの選定基準において
そのアーキテクチャパターンが好きになれるかどうか
をあげています。言語の選定も同じで、開発のモチベーションを高く保つためにも好きな技術を選択するというのは重要な要素の1つです。
進め方
Swift化を進める上で重要になってくるSwiftの特徴として 他の言語との連携 があげられます。 Objective-CとSwiftは相互に連携できるため、フルリプレイスの必要はなく、部分的にSwift化していくことが可能です。 そのため、以下のような方針で進めていました。
- 新規に実装する部分はSwiftで書く
- 既存のObjective-Cのコードは順次Swiftに置き換える
既存のObjective-Cのコードを書き換える単位は特に決めておらず、その時その時で最適と思われるやり方を選択してきました。
一番大きな単位だとサービス単位。m3.comアプリは複数のサービスを集めたプラットフォームとなっています。 その1つのサービスに関連するモジュールをまとめてSwift化します。 対象のサービスに関連するモジュール間では密なやりとりが必要になってきますが、一気にSwift化することでObjective-CとSwiftの境界での制限を気にする必要がなくなります。 また、動作確認もまとめて行うことができるというメリットもあります。
小さいものだとメソッド単位。Swiftにはextension
という機能があり、既存のクラスに機能を付け足すことができます。
これはObjective-Cのクラスについても可能です。
Objective-Cからextension
に定義したSwiftのメソッドを呼ぶことができます。
難しい点
ヘッダファイル(.h)の変換
Objective-Cのヘッダファイルについては、Xcodeの "Generated Interface" という機能でSwiftに変換することができます*3。 しかし、うまくいかないことが多いです。
例えば、key
という文字列を指定して何かしら更新を行う以下のようなメソッドがあったとしましょう。
// Objective-C - (void)updateForKey:(NSString *)key;
これを Generated Interface で自動変換するとこうなります。
// Swift open func update(forKey key: Any!)
型がAny!
だったりと非常に残念です。。
またObjective-Cにとっては以下も同じシンボルのメソッドになります。
このようなことが起きるのはSwiftでは明確に区別されているメソッド名と先頭のパラメータ名を、Objective-Cでは区別していないためです。
open func updateFor(key: Any!)
メソッド定義と呼び出し側でこのようなズレが発生した場合はコンパイルエラーになって気づくことができますが、もし親クラスに同じメソッドが定義されていた場合、コンパイルエラーにはならず、親クラスのメソッドが呼ばれるようになってしまいます。
Objective-Cではメソッドのoverride
を明示しないため自動変換ツールは気づいてくれません。
※ ちなみに、例にあげたメソッドをSwift化するならパラメータにラベルをつけて、以下のように書きたかったりします(個人的な好みです)。
func update(for key: String)
実装ファイル(.m)の変換
ヘッダファイルのインタフェースの変換が一筋縄ではいかないことからもわかる通り、その呼び出しを実装している側も同様に一筋縄ではいきません。 また、実装の詳細をSwiftらしい書き方にしておきたいというのもあります。
- 早期リターンは
guard
を使う - for文は
forEach
やmap
などに置き換える Int?
などのオプショナル型を使う
などなど。
ツールを使ったSwift化について参考にしたりしましたが、大量にエラーが出るため逐一手直しは必要になってくるようです*4。 個人的な見解ですが、Swift化を急ぐ必要がない場合はツールなしの全手動でやるのもありかと思います。
コードレビュー&テスト
まとめてSwift化するとそれなりの量になってしまうため、コードレビューも結構ツラミが出てきてしまいます。
しかし実際にレビューしてみると、致命的なバグが発見されることもあり油断できません。
ユニットテストの重要性を噛み締めることもありました。
歴史を振り返る
Swift化が完了した記念に、プロジェクトにSwiftが導入されてからフルSwift化が完了するまでを、リポジトリの履歴を追って振り返ってみました。するといくつかのフェーズが見えてきたのでまとめてみます。
お試し期(2014年10月〜2015年6月)
プロジェクト内にSwiftのファイルが登場して、Swift化が始まるまでの期間です。 Swiftを導入した当初はまだフルSwift化という方針ではなかったようで、Swiftのファイルは微増しつつ、Objective-Cのファイルも追加されています。 Swiftのバージョンは1.0〜1.2の時期です。
Swift化開始(2015年6月〜2015年9月)
この辺りでSwift化が始まりました。たくさんの改良がされたSwift1.2のリリースも関係しているようです。 Swift化の対象となったのは、ユーティリティ的なモジュールや、Entityクラスなどです。複雑なロジックを持たない呼び出される側のモジュールということになります。
停滞期(2015年10月〜2016年2月)
Swift化がほぼ止まっているように見える期間がありました。 新規機能の開発や既存機能の改善に集中していた時期のようです。Objective-Cのコードは減っていませんが、Swiftが占める割合は少しずつ増えています。 また、この期間にSwift1.2から2.0のバージョンアップも実施されていました。
再開!(2016年2月〜2017年3月)
再びSwift化が激しくなります。ここで主要な機能のSwift化が進みました。
この期間でSwiftのバージョンも3.0に上がります。
次のフェーズとの境界はハッキリ別れるわけではないですが、勢いがだんだんと緩やかになってくるので分けています。
安定期(2017年4月〜2019年2月)
私がジョインしたのは2017年7月なので、このフェーズに入って間も無くです。
このフェーズでは残りのObjective-Cのコードを少しずつSwift化していきました。
残っているのは、あまり更新がない機能や、各サービスの共通基盤的に使われているモジュールです。
共通部分についてはもっと早くやっておいてもよかったかもしれません。後手になる程、機能追加によって影響範囲が広くなっていたからです。しかし急ぐ必要がなかった以上、最後にするのも悪くない選択だったとも思います。
この間にSwiftのバージョンも順次アップデートを実施し、現在ではリリース版としては最新の4.2となっています。
Swift 100%(2019年2月〜)
Swift化が完了し、今に至ります。
最後に
Objective-Cのコードが残っているときは 「あの機能を修正しないといけないけど、Objective-Cなんだよなぁ...」「Objective-Cでこれってどう書くんだっけ?」など考えたりしたこともありましたが、今後はそのようなことは考えずに済みそうです(笑)。 Swift化が完了した今後は、そのプログラミング言語の特徴に恥じないよう、より安全・高速なアプリを開発していければと思います!
以上、iOS版 m3.comアプリのSwift化について紹介させていただきました。
We are hiring
エムスリーではiOSエンジニアも募集中です。 興味を持たれた方はぜひ以下のリンクからご応募ください。
*1:ホットペッパービューティーのiOSアプリのフルスクラッチSwiftリプレイス https://engineer.recruit-lifestyle.co.jp/techblog/2017-12-06-hpb-swift/
*2:PEAKS iOSアプリ設計パターン入門 https://peaks.cc/iOS_architecture
*3:Objective-CのコードをSwift化するXcode7の新機能 "Generated Interface" https://qiita.com/WorldDownTown/items/fc345cc8a90ad8dc3cf7
*4:3年間作り続けて来たアプリをSwift化した話 https://developers.cyberagent.co.jp/blog/archives/6185/