SwiftGenを使ってみた


はじめに

アプリの画面作成においてStoryboardをメインに使用しており、ColorImageをアセットフォルダで管理しています。
Storyboard名ColorImageのタイポの可能性をなくしたいと考え、コード生成ライブラリの一つであるSwiftGenを使用してみました。

SwiftGenとは

説明不要かもしれませんが、SwiftGenは、プロジェクトのリソース(画像やローカライズされた文字列など)に対してSwiftコードを自動的に生成し、タイプセーフな状態で使用できるようにするツールです。
SwiftGenを使用することには、いくつもの利点があります。

There are multiple benefits in using this:
- Avoid any risk of typo when using a String
- Free auto-completion
- Avoid the risk of using a non-existing asset name
- All this will be ensured by the compiler and thus avoid the risk of crashing at runtime.
(https://github.com/SwiftGen/SwiftGen より引用)

タイポのリスク回避や、存在しないアセットの使用リスクの回避などが挙げられています。
また、Swift製のStencilというテンプレート言語を使用することで、適切なSwiftのバージョンでコードを生成できます。

ただし、解析対象は限られており、

  • Asset Catalog
  • Colors
  • Core Data
  • Fonts
  • Interface Builder (Storyboard)
  • JSON と YAML
  • Plists
  • Strings (ローカライズ)

が挙げられています。
既に多くの記事で言及されていると思いますが、Xibは適用外なので、別の方法でタイポを防ぐようにする必要があります。

導入手順

SwiftGenのインストール

Cocoa Podsでインストールしました。

Podfile
pod 'SwiftGen'

Cocoa Podsを使用しましたが、SwiftGenはアプリ側での設定はほぼなく、swiftgen.ymlファイルという設定ファイルに「どのファイルを解析するか」、「どのテンプレートを使用するか」、「生成されたファイルをどこに置くか」を指定するだけで使用できます。

設定ファイル (swiftgen.yml)の作成

まず、設定ファイルswiftgen.ymlを生成するために、プロジェクトのルートディレクトリで以下のコマンドを実行します。
(初めての場合は、HomebrewなどでSwiftGenをインストールしてから以下を実行してください。)

$ swiftgen config init

これで、カレントディレクトリにswiftgen.ymlファイルが作成され、中身は各種設定例がコメントアウトされたものとなっています。

swiftgen.ymlの構成

次に、Swiftgen.ymlを編集します。
例えば、今回はColor、Image、Storyboardのコード生成したいので、以下のように書けます (あくまで一例です)。

swiftgen.yml
input_dir: [プロジェクト名]
output_dir: [プロジェクト名]/Generated/

xcassets:
  inputs:
    - Resources/Colors.xcassets
    - Resources/Images.xcassets
  outputs:
    - templateName: swift5
      params:
        forceProvidesNamespaces: true
      output: Assets.swift

ib:
  inputs: .
  outputs:
    templateName: scenes-swift5
    output: Storyboards.swift

中身の簡単な説明です。

  • input_dirはフォルダパスです。Storyboardやアセットフォルダなどの生成元の共通のフォルダパスを指定しています。
  • output_dirは生成したコードをどこに置くかパスを指定しています。生成したコードを置くためのGeneratedディレクトリを作成しました。
  • xcassetsはアセットフォルダ、ibはStoryboardが解析対象であることを指定しています。
  • inputsは解析対象のファイルパスを示します。inputs_dirを設定してる場合、inputsinputs_dirからの相対パスであることに注意が必要です。
    Colors.xcassetsImages.xcassetsを置くResourcesフォルダを作成しています。また、「. (ドット)」を指定することで、プロジェクトディレクトリの中身のStoryboard全てを対象にしています。
    Storyboardは画面ごとに作成しているため、ディレクトリが分かれており、このように書けるのはありがたいですね。
  • templateNameには先述したテンプレートを指定します。
    使用できるテンプレートのリストを確認したいときは、以下のコマンドを実行して確認できます。
$ swiftgen template list
  • outputは生成コードのファイル名です

設定ファイルのlint

swiftgen.ymlファイルの中身が正しく書かれているか、以下のコマンドでチェックできます。

$ swiftgen config lint

少しハマったのが、余計なスペースなどがあるとエラーになるので、なぜか上手くいかないときはインデントを揃えてみると良いと思います。

SwiftGenを実行する

設定ができたらPods/SwiftGen/bin/swiftgenを実行すれば、指定したパスにコードが生成されます。
ここまで、ビルドなしでコードが生成でき、使用できるのも特徴の一つです。

ただし、swiftgen.ymlを変更するたびに実行するのが面倒な場合は、XcodeRunScriptに以下のように記述しておけば、ビルドのたびに差分を見て変更があればコードを生成してくれます。

${PODS_ROOT}/SwiftGen/bin/swiftgen config run --config $SRCROOT/swiftgen.yml


今回の記事の例だと、実行結果は以下のようなファイル構成になります。

使用例

今回、StoryboardとAsset Catalogを対象にコード生成しました。それらの使用例は以下のようになります。

Asset Catalog

//before
imageView.image = UIImage(name: "cat")
//after
imageView.image = Asset.Images.cat

Storyboard

//before
let hogeVC = UIStoryboard(name: "Hoge", bundle: nil).instantiateInitialViewController() as! HogeViewController
//after
let hogeVC = StoryboardScene.Hoge.initialScene.instantiate()

最後に

SwiftGenを使用してみた感じ (主観)としては、

メリット

  • ビルドでもコマンドからでもコード生成できる
  • 解析対象や生成ファイルのパスを柔軟に設定できる
  • 解析対象の名前を大文字始まりにしていても、キャメルケースでコード生成してくれる
  • 何よりタイポの可能性を減らすことができる
  • swiftgen.ymlファイルさえあれば、同じルールでコード生成できる

一応デメリット

(他のコード生成ライブラリを使用したことがないため、比較はしていません)

  • Xibファイルからコード生成できない (自分は必要としているため)
  • 一番最初にコード生成したときは、生成したファイルをXcodeにわざわざ追加しないと参照されない
    (これについては、原因が分かっておらず、もし読んでいただいた方でわかる方がいれば、コメントいただけると幸いです。)

今後も使用していくと思うので、追加で分かったことや修正があれば追記していきます。

参考文献

この記事は、以下の情報を参考にしました。