SwiftGenを使ってみた

# はじめに
アプリの画面作成において`Storyboard`をメインに使用しており、`Color`や`Image`をアセットフォルダで管理しています。
`Storyboard名`や`Color`、`Image`のタイポの可能性をなくしたいと考え、コード生成ライブラリの一つである[SwiftGen](https://github.com/SwiftGen/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`を設定してる場合、`inputs`は`inputs_dir`からの**相対パス**であることに注意が必要です。  
`Colors.xcassets`と`Images.xcassets`を置く`Resources`フォルダを作成しています。また、`「. (ドット)」`を指定することで、プロジェクトディレクトリの中身のStoryboard全てを対象にしています。  
Storyboardは画面ごとに作成しているため、ディレクトリが分かれており、このように書けるのはありがたいですね。
- `templateName`には先述したテンプレートを指定します。  
使用できるテンプレートのリストを確認したいときは、以下のコマンドを実行して確認できます。

```
$ swiftgen template list
```
- `output`は生成コードのファイル名です

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

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

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

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

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

<img width=700 alt=runscript src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667697/df63824f-1c4c-a026-a553-7cfe0a505ea3.png">
今回の記事の例だと、実行結果は以下のようなファイル構成になります。
<img width=300 alt=folderconfig src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667697/b5fb8cb0-f22b-e34b-9c86-ac3d6b28f957.png">

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

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

###Storyboard

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

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

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

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

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

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

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

- [SwiftGen/SwiftGen (公式)](https://github.com/SwiftGen/SwiftGen)
- [SwiftGenを使ってみた](https://re-engines.com/2019/09/17/swiftgen/)
- [プロダクトにSwiftGenを導入してタイポ0の世界を作る](https://moneyforward.com/engineers_blog/2018/08/07/swiftgen-typographical-error-zero/)
- [SwiftGen](https://qiita.com/usamik26/items/292ae8ab7cb491309932)