SwiftGenを導入して無駄なビルド負担を低減する

14日目、今年も残るところ17日ですね⛄️

今年初めて導入した **[SwiftGen](https://github.com/SwiftGen/SwiftGen)** について紹介します。


### 0. Abstract


「ジェーーンは、すごく賢い！」


そんなこんな評判を耳にして、今年の開発プロジェクトに導入しました。

事前に下調べをして導入しようと早速ググっみたものの、
エンジニアブログなどの日本語での記事が想像していたよりも少なく、
あまり日本では導入していないのか、それとも他に何か理由でもあるのか...。

実際にGoogleでの検索結果は


| |SwiftGen|R.Swift|
|:---|:---:|:--:|
|Googleでのヒット数| 21,600 件 | 487,000,000 件 |

[R.Swift](https://github.com/mac-cain13/R.swift)と比較して圧倒的に情報量が非常に少ない... 😭

一方、githubのコミット状況はどうかというと、こんな感じです。

| |SwiftGen|R.Swift|
|:---|:---:|:--:|
|contributers| 53 | 30  |
|commits | 1421 | 1001 |
|releases | 46 | 66 |
|branches | 6 | 5 |

（12/09/2018時点 by github）

コミッターの数や開発中のブランチでみると、非常に勢いを感じます。
（さすがに [R.Swift](https://github.com/mac-cain13/R.swift) ではリリース回数では及びませんね...）

### 1. SwiftGenとは？

Swiftプロジェクトで使用される画像イメージやフォント, segue等のようなリソース名を自動的に生成し、型付を行ってくれるライブラリです。
基本的な使用目的としては, [R.Swift](https://github.com/mac-cain13/R.swift)と同じです。

* 文字列を利用する際に余計なTypeを回避できる
* 自動補完機能を利用できる
* 存在しない asset を使用してしまうリスクと回避できる
* コンパイル時にこれら全てをチェックできる



### 2. R.Swiftと何が違うか

SwiftGenの実体はライブラリではなく、コマンドライン・ツールです。
この点が R.Swift と大きく異なります。

**メリット**
* 開発の活発頻度が高い
* 定期的なアップデートが望めそう 
* ライブラリのインポートが必要ない
* 生成するリソース種別を取捨選択できる
* [R.Swift](https://github.com/mac-cain13/R.swift)のように再ビルドが走らない

**デメリット**

* 導入例が多くない
* 日本語翻訳されたドキュメントや説明が少ない
* R.Swiftよりも導入が手間（後述）
* stringdictに非対応



**R.Swiftとの比較**
有名なリソース管理ライブラリとして、R.Swift がよく知られています。

| |SwiftGen|R.Swift|
|:---|:---:|:--:|
| インストール  | CocoaPods, Zip, Homebrew, Mint, Compile from source | CocoaPods, Zip, SPM |
|導入| やや面倒 | 簡単 |
|制御 | 不要な機能は disable | 全部 |
|ビルド | クラスがリソースごとに分かれているため、毎回全ビルドが走ることはない | リソースファイルに増減がある度に R.generated.swift の全ビルドが走るので、大規模プロジェクトでは時間がかかる |
| 対象 | Image, Font, Color, Storyboards, Localizable.strings | Image, Font, Color, Storyboards, Localizable.strings, Segues, Nibs, Reusable cells |

もしこれから導入を考える場合には、こちら [Medium](https://medium.com/@mono0926/%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88%E3%81%82%E3%82%8A%E3%81%8C%E3%81%A8%E3%81%86%E3%81%94%E3%81%96%E3%81%84%E3%81%BE%E3%81%99-3606c3dc5440) も参考になります。


### 3.導入方法

早速、導入してみましょう。

#### 1. インストール

##### CocoaPodsから

Podfileに `pod 'SwiftGen'` を記述し、`pod install`を実行する。

```sh:Podfile
target 'アプリ名' do
  # Comment this line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for SampleLocation
  pod 'SwiftGen'

end
```

CocoaPods で SwiftGen を追加すると、swiftgen コマンドがプロジェクト内に追加されます（CocoaPods は一般にはライブラリを追加するツールですが、ここではライブラリではなく Pods/ にバイナリが追加されるのみです）。

```sh:Run_Script_Phase
"$PODS_ROOT"/SwiftGen/bin/swiftgen
```
ビルド時にswiftgenコマンドが実行されます。

##### Homebrewから

Homebrewでインストーする場合、システム全体にインストールされます。
同じマシンならば、同じバージョンのswiftgenで処理されます。
もしもチーム開発する場合、必ずバージョンを揃えておく必要があります。

```
$ brew update
$ brew install swiftgen
```

#### 2. 設定ファイルの作成

プロジェクトファイル直下に `swiftgen.yml` を作成します。
[SwiftGen Configuration File](https://github.com/SwiftGen/SwiftGen/blob/master/Documentation/ConfigFile.md)


```sh:例.swiftgen.yml
// 画像リソース用
xcassets:
  paths:  "$PROJECT_NAME"/Resources/Assets.xcassets                 # 入力パス
  templateName: swift4                                      　　　　　　　　　　　　　　　　　　# テンプレート指定
  output: "$PROJECT_NAME"/Resources/Generated/DefaultAsset.swift     # 生成パス

// ローカライズ用
strings:
  paths: "$PROJECT_NAME"/Resources/Strings/en.lproj/Localizable.strings
  templateName: structured-swift4
  output: "$PROJECT_NAME"/Resources/Generated/Localized.swift

// Storyboard用
storyboards:
  paths: "$PROJECT_NAME"/Scenes/Storyboards/Base.lproj
  templateName: swift4
  output: "$PROJECT_NAME"/Resources/Generated/Storyboards.swift

...
```

- 入力パス: 読み取り対象（リソース）のパス名をします。
- 出力パス: 自動生成されたソースファイルを出力するパスを指定します。
- templateName: Swiftバージョンに一致するテンプレートを指定します。

templateNameでは、生成コードに関するテンプレートを指定できます。
設定可能なテンプレートを確認するには、以下のコマンドでを確認できます。
生成されるソースコードの構造（structured　or flat）も選べるようです。

```
	$ swiftgen templates list
```

```yml:Templete
	xcassets:
	  custom:
	  bundled:
	   - swift3
	   - swift2
	   - swift4
	colors:
	  custom:
	  bundled:
	   - literals-swift4
	   - swift3
	   - swift2
	   - literals-swift3
	   - swift4
	strings:
	  custom:
	  bundled:
	   - flat-swift4
	   - structured-swift4
	   - structured-swift2
	   - flat-swift3
	   - flat-swift2
	   - structured-swift3
	storyboards:
	  custom:
	  bundled:
	   - swift3
	   - swift2
	   - swift4
	fonts:
	  custom:
	  bundled:
	   - swift3
	   - swift2
	   - swift4
	---
```

テンプレートは独自に定義することも可能です。
より詳しい情報は[こちら](https://github.com/SwiftGen/SwiftGen/blob/master/Documentation/Creating-your-templates.md)を参照してみて下さい。

#### 3. 2をチェックします。

　`swiftgen config lint` を叩いてみましょう。

```
$ swiftgen config lint

Linting swiftgen.yml
> Common parent directory used for all input paths:  {_PROJECT_NAME_}/Others
> Common parent directory used for all output paths: {_PROJECT_NAME_}/GeneratedCodes/
> 1 entry for command strings:
  $ swiftgen strings -t structured-swift4 -o {_PROJECT_NAME_}/GeneratedCodes/L10n-Constants.swift Demo-App/Others/ja.lproj/Localizable.strings
```

#### 4.`swiftgen`コマンドを実行する

```
＄ swiftgen config run

  or

$ swiftgen　　　# 省略形
```

swiftgenコマンドが正常に通った場合、出力先のディレクトリにはファイルが生成されます。

これで、自動補完ができるようになります。

### 4. その他

#### インテグレート

Xcodeに **Run Script Phase**  を追加し, 
ビルド実行をトリガーにして自動でコード生成を行います。
（※CocoaPodsからインストールすれば、この設定は必要ありません）


1.	左上のProject Navigatorを選ぶ
2.	アプリのTargetを選ぶ
3.	"Build Phases" タブに移動する
4.	上部の左角付近のボタン「+」をクリックし, "New Run Script Phase" を選ぶ
5. 以下のようにスクリプトを記述する

* ケース1. CocoaPods

```
	"$PODS_ROOT"/SwiftGen/bin/swiftgen
```

* ケース2. ZIP解凍file

```
	"$PROJECT_ROOT"/SwiftGen/bin/swiftgen
```

* ケース3. Homebrew

```
	if which swiftgen >/dev/null; then
  		swiftgen
	else
  		echo "warning: SwiftGen not installed, download it from https://github.com/SwiftGen/SwiftGen"
	fi
```

#### ディレクトリ単位でパス指定

swiftgen.ymlからの相対パスで, .stringsの相対パス, Swiftファイルの出力先を設定することもできます。

```
	# 入力用の親ディレクトリ
	input_dir: Demo-App/Others
	
	# 出力用の親ディレクトリ
	output_dir: Demo-App/GeneratedCodes/
	
	# 文字列用の設定
	strings:
	  paths: ja.lproj/Localizable.strings
	  templateName: structured-swift4
	  output: L10n-Constants.swift
```

[SwiftGen Configuration File](https://github.com/SwiftGen/SwiftGen/blob/master/Documentation/ConfigFile.md)

[Localizing iOS app with SwiftGen](http://catqbat.com/localizing-ios-app-with-swiftgen/)

### 5. さいごに

実際に導入してみての感触として、ビルドを必須としないところがいい感じです。どちらが優秀かという点については、CocoaPods vs Carthage論争と同様、最終的に導入コスト、環境依存性、カスタマイズ性などを総合して判断するのが一番良いかなと思います。

私はプロジェクトファイル直下で Rakefile を定義することがあるので、
以下のように swiftgen コマンドも追加してみました。

```sh:例.Rakefile
#xcprojectのファイル名
PROJECT_NAME = '...'

# xcworkspaceのファイル名
WORKSPACE_NAME = '....'

desc 'Xcodeを開く'
task :open do
  sh 'open', "#{WORKSPACE_NAME}.xcworkspace"
end

desc 'Carthageのビルド'
task :carthage do
  sh "carthage update --platform iOS"
end

desc 'Podsの準備'
task :pod do
  sh "bundle install --path vendor/bundler"
  sh "bundle exec pod install"
end

...

desc 'SwiftGenの実行'
task :swiftgen do
  sh 'swiftgen config lint'
  sh 'swiftgen config run'
end

```

画像やStoryboard、stringsファイルを追加・変更したとき、

```
$rake swiftgen
```
をコマンドで叩けるようにしています。