4月にミラティブに入社したiOS開発者の福山 (@fokotate)です。ミラティブにはゲーム好きな人が多いためか、積みゲー消化が急速に進んでいます。
今回は珍しいケースかと思いますが、他社のiOSプロジェクトでも起こりえる問題に対処することができたので共有させてください。
2022/06/09 ✍️追記: Xcode 14 beta 1で確認したところ、以下の問題はほぼ解決しているようでした。AppleにFeedbackを送ったところ同様の報告が複数あり、対応していただけたようです。
Mirrativ iOS開発の問題点
Mirrativ iOS開発ではSwiftGenを使い、多言語対応ファイルLocalizable.stringsをswiftファイルに変換しています。これによって文言をコードに挿入するときに、Xcodeのコード入力の自動補完が使えるようになりタイピングミスがなくなり、コンパイル時もチェックされるというメリットがあります。
// SwiftGenを使用しないケース label.text = NSLocalizedString("text_random", comment: "") // SwiftGenを使用したケース: コードの自動補完がはたらき、コンパイルチェックもされる label.text = L10n.textRandom
しかし、Mirrativ iOSのLocalizable.stringsは約4600行と肥大化*1しており、これをSwiftGenで1つのswiftファイルに変換すると約12000行になっていました。
その巨大swiftファイルを開くと恐ろしいことにXcode(執筆時点v13.3.1)がフリーズ🥶してしまいます。いくら待ってもレインボーの回転マークが消えず、強制終了するしかありません。M1 MaxモリモリMacでも不可避です。
私は最初Xcode以外の軽量エディタVSCodeで開けばいい程度に考えていましたが、気をつけていてもうっかりXcode上で参照からその巨大swiftファイルを開いてしまうこともあり、他の開発者も嘆いていました🥺
そこでSwiftGenの設定を変えて、その巨大swiftファイルを分散できればXcodeがフリーズすることもなくなるのではと考え調べてみることに。
改善方法
ステップ1 - Localizable.stringsの分割
ここの分割は手動でも可能ですが、Mirrativの開発では元となる多言語対応リストをクラウド上の表計算ソフトで管理しており、スクリプトを用いてLocalizable.stringsに流し込んでいます。ここはスクリプトを調整することで800行で一つのLocalizable{n}.stringsを生成するようにし、Localizable1.strings, Localizable2.strings...というように6つに分割しました。また、それらがそれぞれBase, en, jaの3言語あるので、3x6の18ファイルを自動生成しています。
ステップ2 - swiftファイルの分割
分割したLocalizable{n}.stringsをそれぞれswiftファイルStrings{n}.swiftへ変換していきます。
通常SwiftGenは1つのswiftファイルにenum L10n
を作成します。これを複数のswiftファイルに分割するために、ベースとなるenum L10n
を拡張するextension L10n
として書きだせるようにSwiftGenを調整します。
しかし、SwiftGenにはextension L10n
として書き出すオプションがありません。そこでSwiftGenのカスタムテンプレート機能を利用することを考えました。
参照: Customize SwiftGen templates
SwiftGenに標準で付いているテンプレートを複製して一部を改変しカスタマイズテンプレートとして使います。
$ swiftgen template cat strings structured-swift5 >custom-structured-swift5-template.stencil
複製したファイルcustom-structured-swift5-template.stencilの62行目を変更。enumではなくextensionとして出力するように。
- {{accessModifier}} enum {{enumName}} { + extension {{enumName}} {
6つのLocalizable{n}.stringsをそれぞれStrings{n}.swiftとして出力する設定を記載
swiftgen.yml
strings: - inputs: Supporting Files/Base.lproj/Localizable1.strings outputs: - templatePath: custom-structured-swift5-template.stencil output: Strings/Strings1.swift - inputs: Supporting Files/Base.lproj/Localizable2.strings outputs: - templatePath: custom-structured-swift5-template.stencil output: Strings/Strings2.swift - inputs: Supporting Files/Base.lproj/Localizable3.strings outputs: - templatePath: custom-structured-swift5-template.stencil output: Strings/Strings3.swift - inputs: Supporting Files/Base.lproj/Localizable4.strings outputs: - templatePath: custom-structured-swift5-template.stencil output: Strings/Strings4.swift - inputs: Supporting Files/Base.lproj/Localizable5.strings outputs: - templatePath: custom-structured-swift5-template.stencil output: Strings/Strings5.swift - inputs: Supporting Files/Base.lproj/Localizable6.strings outputs: - templatePath: custom-structured-swift5-template.stencil output: Strings/Strings6.swift
手動でベースとなるenum L10n
をもつswiftファイルStrings.swiftを配置 (SwiftGenはextension L10n
を生成する)
Strings.swift
import Foundation enum L10n { }
これでSwiftGenを実行するとLocalizable{n}.stringsからStrings{n}.swiftを生成してくれます。
$ swiftgen
まとめ
巨大な自動生成Swiftファイルの分割に成功しXcode上でフリーズせず1秒以内に開くことができiOS開発者のストレスを確実に減らしました🎉
- 約4600行のLocalizable.stringsを6つに分割 (各ファイル800行以下)
- 約12000行のStrings.swiftも6つに分割 (各ファイルは約2200行 - この辺りが現在のXcodeの許容範囲)
今後のXcodeがアップデートで改善してくれる可能性もありますが、より良い方法などありましたら是非教えてください!
We are hiring!
ミラティブでは一緒に開発してくれるiOS開発者を募集しています!少しでも興味を持っていただいた方はお話を聞いていただくだけでも結構ですので、気軽にご連絡ください。
エンジニア向け会社紹介資料 speakerdeck.com
*1:Localizable.strings肥大化の一因として、iOS以外のプラットフォームで使われている文言も一括で取り込んでいることもあり、今後そちらの整理も予定されております。