Mirrativ Tech Blog

株式会社ミラティブの開発者(バックエンド,iOS,Android,Unity,機械学習,インフラ, etc.)によるブログです

SwiftGenが生成する1万行越えSwiftファイルでXcodeがフリーズするのを防ぐ

4月にミラティブに入社したiOS開発者の福山 (@fokotate)です。ミラティブにはゲーム好きな人が多いためか、積みゲー消化が急速に進んでいます。

今回は珍しいケースかと思いますが、他社のiOSプロジェクトでも起こりえる問題に対処することができたので共有させてください。

2022/06/09 ✍️追記: Xcode 14 beta 1で確認したところ、以下の問題はほぼ解決しているようでした。AppleにFeedbackを送ったところ同様の報告が複数あり、対応していただけたようです。

Mirrativ iOS開発の問題点

Mirrativ iOS開発ではSwiftGenを使い、多言語対応ファイルLocalizable.stringsをswiftファイルに変換しています。これによって文言をコードに挿入するときに、Xcodeのコード入力の自動補完が使えるようになりタイピングミスがなくなり、コンパイル時もチェックされるというメリットがあります。

SwiftGenの変換: Localizable.strings -> Strings.swift

// 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の分割

Localizable.stringsを6分割 (1つ当たり800行以下)

ここの分割は手動でも可能ですが、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へ変換していきます。

各Localizable{n}.stringsを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開発者を募集しています!少しでも興味を持っていただいた方はお話を聞いていただくだけでも結構ですので、気軽にご連絡ください。

www.mirrativ.co.jp

エンジニア向け会社紹介資料 speakerdeck.com

*1:Localizable.strings肥大化の一因として、iOS以外のプラットフォームで使われている文言も一括で取り込んでいることもあり、今後そちらの整理も予定されております。