[11/21開催]完全Swift化を遂げたネイティブアプリのこれまでとこれから
2022年11月21日に開催されたCTO meetupのテーマは、iOSアプリコードの完全Swift化です。今回はほぼ同時期に脱Objective-Cを推進した「note」と「楽天ラクマ」の開発責任者の方々にご登壇いただき、どのようにリファクタリングを行ってきたのか、その中でどのような課題にぶつかり解決を図ったのか解説いただきました。
今後両社が目指していきたい技術や世界観の在り方についても答えていただいているので、ぜひチェックしてください。
各社ネイティブアプリのこれまで
noteのコードから変化を見る | 開発をドライブするためにシンプル化
くれいん氏:
まずは、完全Swift化へ舵を取るきっかけから入りたいと思います。かっくんさんお願いします。
かっくん氏:
noteでiOSアプリのエンジニアを務めているかっくんです。以下に出ているのは、noteのアプリがどういう言語をどのぐらい保持していたのかを数字で示したものです。
かっくん氏:
左側は私が入社した2020年5月、右側が2022年11月現在のコード量です。古いつくりの画面が多く、更に大半がObjective-Cでできていたので、私の入社以降、古い言語や画面を洗い出してリニューアルするプロジェクトをスタートしました。1年半ほどかけてあらかた新しい画面に変更し、内部事情による1年の停滞を経てからObjective-Cをゼロにしようと目標を立て、今に至ります。
くれいん氏:
JSONが減り、JavaScriptファイルが増えているのが面白いですね。
かっくん氏:
Image Assetの中にあったJSONファイルをカウントしているのだと思います。使っていないリソースを削減したり、画像をほとんどSF Symbolsに寄せたりしたために減っています。
JavaScriptファイルが一つ増えているのは、WebViewの中の検索機能をあるタイミングで追加し、JavaScriptファイルで書き出したからですね。
くれいん氏:
noteさん特有ですね。
noteのアーキテクチャから変化を見る | テスタビリティを向上
くれいん氏:
続いてアーキテクチャについて、技術選定の理由なども交えてお話しいただけますか?
かっくん氏:
今までは昔ながらのMVCに近しいViewModelという存在があり、これがAPIなどとやり取りするアーキテクチャでした。これをいわゆるVIPERに近しいアーキテクチャに変えています。ViewControllerがさまざまな役割を持ってしまい、テストがしづらい問題があったためです。
なるべくPresenterがDIでいろんなものを受け取り、それぞれをコントロールするアーキテクチャにしてテストができる形にしようとしました。
もともとあったテスト自体も複雑なロジックに寄せていてカバレッジが低かったため、今は画面ごとにテストを入れてカバレッジを向上する活動もいくつかやっています。
くれいん氏:
例えばロジックとUIテストの2つがあると思うのですが、どちらかというとロジックメインですか?
かっくん氏:
E2EにはMagicPodを使っていて、最低限UIテストをしたい部分は実施しています。画面のレイアウト崩れが心配な場合は、スナップショットテストを使っていますね。
ラクマのコードから変化を見る | テスタブルな体制を整備
くれいん氏:
では次に、ラクマ側の話をお願いします。
だーくろ氏:
beforeは私が入社した2018年のコードです。Objective-Cが50%を超えていて、大きな画面はObjective-Cで実装している状態でした。100%Swift化したのは2022年の9月頃で、現在は上記のような状態です。
SwiftだけではなくMakefileを使って環境設定を自動化していますし、CIはBitriseを使っているので、そのワークフローをYAMLで書いたりもしています。
JSONが多いのはユニットテストで使っているスタブのデータを、そのままJSONで直で書いているためですね。
くれいん氏:
JSONはファイル数が増えてコード量は減っているのが面白いですね。設定ファイルがすごく増えたようにも見えます。
ラクマのアーキテクチャから変化を見る | フレームワークに合わせて選定
くれいん氏:
次はラクマのアーキテクチャの話です。こちらも導入理由などをお伺いできますか?
だーくろ氏:
2018年に私が入社した時点ではほとんどViewControllerしかない状態で、その中にビジネスロジックやAPIクライアントを叩く実装をしていたり、パース処理も書いてあったりしました。
あとはいろいろなManagerがたくさんあったので、これらを整理しようと方針を立てたのが2019年頃です。
UIKitはMVPの形にしていて、ViewControllerとPresenterとUseCaseがいます。ドメインにはリポジトリとデータストア、API Clientがある感じです。2020年にSwiftUIが出てきた頃からはMVVMのアーキテクトにして、なるべくプラットフォームに合わせた書き換えを行う形にしていますね。
クリーンアーキテクチャに寄せている部分はあるものの、シングルトンの状態管理をするものだけをリポジトリとして置いています。UseCaseからAPIを直接リクエストするようにしているので、そこはラクマの独自実装になっています。
くれいん氏:
ぱっと見複雑そうに見えますが、意外に一方通行で開発しやすいですし、テストも書きやすいですよね。
Swift移行における課題と工夫
noteのリファクタリングにおいて特に苦しかった3つのポイント
くれいん氏:
では次に、Swift移行における課題と工夫の話に移ります。
noteさん側からリファクタリングにおいて特に苦しかったポイントとして、「Objective-Cのファイルが依存しているライブラリを剥がせない」「依存が深いクラスがなかなか剥がせない問題」「リファクタリング起因のバグに気づきづらい」の3つを挙げていただきました。
かっくん氏:
一つずつ見ていくと、まずObjective-Cのファイルが依存しているライブラリがありました。モデルがライブラリをインポートしていて深く依存していたので、モデルが消えないとライブラリも消せなかったんです。私たちはCocoaPodsをやめてSPMに移行したかったため、ここがずっと残っているのは辛かったですね。
2つ目は依存が深いクラスがなかなか剥がせなかったことです。重要ではないのに依存が深いせいで変えられない部分があったのが、Objective-Cの移行がなかなか進まなかった理由の一つです。
3つ目はリファクタリング起因のバグがいくつか出てしまったことです。状況的には、Objective-Cでもともと存在していたクラスのModelと新しくSwiftで作ったクラスのModelが両方存在していました。これをObjective-CからSwiftに変換できる形で作ったところ、コードの移行時にちょっとしたバグが生まれていました。例えばプロパティの名前が少しだけ違うといった問題に最後まで気付けずに、後で急いで修正しましたね。
ラクマのリファクタリングにおいて特に苦しかった3つのポイント
くれいん氏:
次はラクマ側の苦しかったポイントです。
特に苦しかったポイントとして、「数千行に及ぶ複数の巨大なObjective-Cクラスを分解しながらの移行」「変更による影響範囲の調査が難しく、テスト漏れが発生したこと」「テスタブルな実装に変えていく必要がある」の3つが挙げられました。
だーくろ氏:
1つ目と2つ目はnoteさんと同じような感じです。数千行に及ぶ巨大なObjective-Cクラスを分解しながら移行していく必要がありました。ラクマは商品中心のアプリなので「アイテム」というモデルがさまざまな画面で参照され、なおかつ条件に応じた検索も可能ですが、それらがObjective-Cの3000~4000行のクラスになっていました。
この部分の分解は最後まで残っていたものの、そのままSwiftに移行するのではなく、ドメインのコンテキストごとに分解したのが上手くいったポイントです。例えば商品の詳細画面で使うモデルクラスと商品の一覧画面で使うモデルクラスは微妙にコンテキストが違うので、別のモデルクラスとして分解していきました。影響範囲がかなり絞られて、上手く分解できたのかなと思います。
3つ目のテスタブルな実装については、もともとView Controllerしかなかったため、それをPresenterやUseCaseに移行してDIしていくのが大変でした。
よくリファクタリングやコンバートでは先にテストを書いて変更を検知すればいいと言われがちなのですが、これはなかなか難しいんです。特にSwiftへのマイグレーションは単純なメソッドのインプットとアウトプットだけを見るわけではなく、内部構造を変える必要があったりします。
どうしても先に一回整理をしてからテストを書く順番にしなければいけませんし、そのためにエラーケースや例外的な部分が漏れてしまうことがありましたね。
noteの工夫 | Objective-Cから変換可能なモデルファイルをSwiftで作成
くれいん氏:
次は、Swift移行における工夫について、以下のスライドを見ながらディスカッションしていきたいと思います。noteさんからいかがですか?
かっくん氏:
先ほどの話にも出ましたが、Objective-Cのモデルがあったときに変換可能なSwiftのファイルを作り、極力Swiftのファイルを使う形で少しずつ移行を進めていきました。
もともとはAPIのレスポンスを受け取った時点でObjective-Cのクラスを使っていたのですが、WebViewに行く頃にはSwiftのモデルを使うようにする形で、グラデーション的に変えています。最終的にAPIから直接Swiftのモデルに変換されて、UIに返ってくるようなやり方をしました。
技術以外の取り組みについては、脱Objective-Cを半期の目標に組み込んだことで「みんなでやっていくぞ」という意識を醸成できたのが、個人的にはすごく良かったと思います。
くれいん氏:
ちなみにそのときのチーム人数は何名でしたか?
かっくん氏:
iOSエンジニアが3名でした。私以外の2名はnoteに入社したばかりだったので、基本的に私の意思決定が強かったですね。
ラクマの工夫 | スタンダードに寄せた技術選定を実施
だーくろ氏:
ラクマ側は技術的な観点でいうと、まずスタンダードに寄せるようにしていました。ラクマはもともとCocoaPodsでしかライブラリ管理をしていなかったのですが、Carthageが出たタイミングでも移行はしませんでした。
XcodeGenが出たタイミングでもトライはしてみたものの、自分たちでメンテナンスができないと判断してやはり見送りました。
2018年あたりはRxSwiftを使ってMVVMのアーキテクチャを作るアプリが多かったと思いますが、リアーキテクチャを考えたタイミングでMVPかMVVMを迷い、結局MVPのほうがiOSのスタンダードなフレームワークとの親和性が高くて安全だろうという考えで、RxSwiftは使わない判断をしました。
実際にCarthegeはXcode12あたりのタイミングでビルドが通らないと界隈が騒がしくなったり、XcodeGenも今は多くの企業が移行しているという話があったりしますね。noteさんは今もXcodeGenを使っているんですか?
かっくん氏:
現役で使っていますね。
だーくろ氏:
それは戻したり剥がしたりするのでしょうか?
かっくん氏:
なるべくやりたいとは思っているものの、現状は厳しいですね。うちはアセットが入る画像やローカライズテキストを一つのアセットにしていて、それがいろいろなところから呼ばれる形にしてしまったんですよ。
スタティックにすると容量が増えるためダイナミックにしたいのですが、そうなるとSPMは辛いです。
あとはSPMだとエクステンション周りで上手くいかない箇所があったりして、そこも踏まえるとSPM化にはなかなか踏み込めていませんね。
だーくろ氏:
ありがとうございます。
続いてラクマの「最小単位でconvertしていく」は、部分的にSwiftのコードに置き換えて、テストでカバーするという意味です。
技術以外の取り組みでいうと、当社には楽天全体の評価制度があるため、それとは別にモバイルエンジニア向けの目標設定にアジャストをして、リファクタリングを定量的に評価できるようにしています。
また、リファクタリングの後半には一つのプルリクエストごとに「インパクトレベル」を4段階で設定して、QAに共有するようにしました。「これはインパクトレベルが4なので、重点的にテストしてください」「これはインパクトレベル1なので影響は少ないです」といったコミュニケーションに使っています。コードレビューも、インパクトレベルが4のときは必ず特定の人のレビューを通すといった条件付けをしています。
リファクタリングペースの調整も実施しました。リファクタリングが残り4%ほどになった頃、チーム全体に「早く置き換えたい」という気持ちが先行したためか、連続的にバグが出始めてしまったんですよね。そこで「一回踏みとどまろう」と話をして、大きいプルリクは投げずに細かい単位で進め、ペースを整えました。
マネージャーの立場として、技術以外のチームビルディング的な部分に取り組むのは面白かったです。
くれいん氏:
評価制度に合わせる、インパクトレベルを設定するというのは、実はすごく絡み合っている部分もあります。
エンジニアはタスクを目標化しやすいのですが、どのぐらいのリファクタリングをしたのかは評価しづらいんですよね。このときに評価の一つの指標になるのが、インパクトレベルです。
3や4レベルのプルリクをこなすと、自分の目標に対してすごく貢献されているとみなされます。難しいリファクタリングは敬遠されがちなので、インパクトレベルが可視化されてモチベーションにつながるのは、すごく良いなと思っています。
各社ネイティブアプリのこれから
今後導入していきたい技術や目指す世界観
くれいん氏:
今後導入したい技術スタックなどについてもそれぞれ比較して掲載してみましたので、noteさんから説明をお願いします。
かっくん氏:
脱XcodeGenに向けて今後もやっていきたいのが、SPMを活用した細かいモジュール分割です。あとはまだ9割5分がUIKitなので、SwiftUIの部分適用も書き換えられるところは進めていきたいです。
将来的に目指すのは、これから入ってくるメンバーがなるべく快適な開発ができるようにすることです。UXもすごく大事にはしていますし、ストレスフリーな開発基盤を作っていきたいです。
くれいん氏:
noteさんの開発は「編集をして投稿する」機能がメインになると思いますが、それをSwift上で実現するのは現時点のSwiftUIではハードルが高いですよね。ネイティブで提供されているものだけだと、やはりほかの画面の書き換えがメインになるのでしょうか?
かっくん氏:
しばらくはUIKitの中にSwiftUIが存在する世界観がメインになるかと思います。おっしゃる通りテキストをエディットする画面はSwiftUIでは厳しいですね。中のデータ管理も大変ですし。
そういうところも含めると、現状はUIKitで細かくマネジメントするのがいいかなと思います。とはいえ、プロフィール設定なんかはプレーンテキストなので、将来的にSwiftUIに移行するでしょう。
だーくろ氏:
ラクマも同じくSwiftUIの拡大を考えています。今もメイン画面でSwiftUIを使っている部分があるので、移行は一定進んできたと思います。
ただどうしてもSwiftUIでは厳しい部分があるため、そこはトレードオフでできるところからやっている感じです。SwiftUIはコードがシンプルで開発しやすいですし、アニメーションの追加など表現力拡大に向けた下準備をしていきたいですね。
あとはエンジニア発案の機能改善拡大を考えています。SwiftUIを使ったUIの表現のほか、アプリで完結できるようなミニ機能も、なるべくエンジニア発のアイデアを実現できればと思います。
くれいん氏:
SwiftUIはデメリットがフォーカスされがちな一方、アニメーションに強かったり簡単な実装で豊かな表現ができたりとメリットも多いです。リリースされて2年経過して、できることも増えていますからね。
エンジニア発案の機能改善拡大というのは、例えば機能のみならずデザイン構造も含みますが、iOSライクな機能やデザインは、やはりエンジニアからのボトムアップで提案したほうがいいんですよね。
ディレクターやプロデューサー、PdM、デザイナーさんたちは、彼らの領域があって忙しいですから。
かっくん氏:
ビジネス的に優先度が高い開発があると思うのですが、エンジニア目線だとリファクタリングや「ユーザー的にはあったほうがいいけれどビジネスには直結しない機能」なんかの優先度が高くなったりしますよね。そのあたりの優先度はどのように決めているんですか?
だーくろ氏:
ラクマの場合は、ビジネスサイドとプロデューサーが決めた1年間の全体的なロードマップがあります。それを基に1~2ヶ月単位でプロジェクト期間が決められるわけですが、アプリエンジニアが実際に実装作業をするのはプロジェクトの後半だったりしますよね。そのリードタイムを有効活用して、リファクタリングやちょっとした機能改善を実施しています。noteさんはどうですか?
かっくん氏:
リードタイムに別のタスクをやっていることは多いですが、noteの場合は目標の中にそもそもリファクタリングの推進が入っているので、タスクとして実行していますね。
一昨年はウィジェット機能が出るから絶対に入れようと決めて、1ヶ月開発を止めてウィジェットに取り組むようなこともありました。9月のiOSのアップデートと一緒に出したかったですし。
くれいん氏:
確かに誰かが「やるぞ」と言わないと始まらない部分はありますよね。
かっくん氏:
当時、うちのチームはPdMがいなかったからやりやすかったのかもしれません。今はPdMのやりたいことと自分たちが優先したいことを各スプリントのミーティングで擦り合わせています。
くれいん氏:
楽天は大きなウォーターフォールの中で小さなアジャイル開発をしていますが、noteさんは少しずつタスクが発生するイメージですね。
かっくん氏:
会社の大きなロードマップはありますが、アプリチームとしては半年の目標の存在が大きいかもしれません。
だーくろ氏:
ちなみに、SPAのモジュール分割をどのぐらいまで考えているのか聞いてもいいですか?
かっくん氏:
テキストをエディットする画面は外に出したいですね。それ自体は1画面ですが、中に非常に細かいモジュールが存在しているので、丸ごと出してしまいたいなと。
また、noteのアプリは画面の下部にタブが5つありますが、その中にあるアカウント設定系も外に出そうかと思っています。
くれいん氏:
ざっくりした切り出しの方向性は、機能単位や画面単位なんですか?
かっくん氏:
社内用語的にわかりやすい画面単位ですかね。エディターはそれ自体が大きすぎるのでそれだけを外に出しますが、各モジュールをある程度気持ちよく開発できる単位にしたいなと思っています。細かすぎると逆に辛いですし。
だーくろ氏:
ラクマが今考えているのは、プレゼンテーションレイヤーを親のモジュールに残しておく方法です。下のレイヤーにあるコアのコンポーネントなど、単純なUIKitやFoundationだけで実現できるコードをまず移動して、APIクライアントやリポジトリあたりのドメインレイヤーを移行できたら、一旦そこで完成にしようかと議論しています。フィーチャー単位ではまだ分けない判断です。
くれいん氏:
将来的な展望としてはやりたいですけどね。2、3年後にまた新しい何かが生まれて混乱するかもしれませんし、ミニマムにやっていく形を取ると思います。
Q&A
Slidoというサービスを活用して、当日はセッションの最後に視聴者の皆さんからの質問に回答いただきました。
CSとは分業体制を取りながら、適宜情報共有や連携をする
Q.アプリチーム起点でユーザーインタビューは実施していますか?CSチームと完全に分業体制でしょうか?
かっくん氏:
当社はユーザーインタビューを行うチームがありデザイナーが1名入って主導しているので、新機能を開発する際などは彼に相談をしています。例えばアクセシビリティ観点では外部に依頼することもありますが、内部で実施する場合は新入社員をインタビュイーにするなど、いろいろな目線がもらえるような形で実施しています。
CSチームは独立しています。何かリリースがあれば内部共有会にCSチームも参加してもらい、問い合わせに備えて機能説明などをしていますね。分業体制ではあるものの、なるべく協力している感じです。
だーくろ氏:
ラクマにはUXリサーチャーがいるので、彼らがインタビューを行っています。どちらかというと新しい機能をリリースするときに実施するパターンが多いかもしれません。
CSは完全に分業制です。技術的な問い合わせはアプリエンジニアが回答することもあります。
エンジニアを悩ませるiOSアップデート
Q.iOSのバージョンアップデートでしんどかったことはありますか?
だーくろ氏:
広告のパーミッションで2回ほどリジェクトされたのがしんどかったですね。
くれいん氏:
アプリ側がボタンを押させるような誘導をするのはNGだと情報が出回りましたね。通り抜けが厳しいケースがあって、確かに辛かったです。
かっくん氏:
うちはUICollectionView Compositional LayoutsというものがiOS14サポートのタイミングで出てきたときに移行しようと思ったのですが、そのために過去動いていた画面がOSのバージョンによって動かなくなることがありました。それを起因にしたバグも出てしまい、個人的には辛かったですね。
くれいん氏:
iOS13.1から2~4までのマイナーアップデートで挙動が違うのもすごかったですよね。ユーザーさんにOSをアップデートしてもらう以外、何もできませんでした。13.4まではバグも多くて、ラクマも急いで14に上げました。
ご質問ありがとうございました。
今回のCTO meetupは以上です。ありがとうございました!
まとめ
完全Swift化に向けてiOSアプリのリファクタリングを進めたnote、楽天ラクマの事例はいかがでしたか。リファクタリング時の苦悩のほか、実際に採用した技術、半期目標への組み込みや評価への反映といった技術以外の取り組みまで語っていただきました。
各社注目しているSwift UIの今後にも注目です。