R.swiftとSwiftGenの導入方法とどちらを採用した方がいいのか

[CA Tech Dojo/Challenge/JOB Advent Calendar 2019](https://qiita.com/advent-calendar/2019/dojo-challenge-job)の18日目は[@ostk0069](https://qiita.com/ostk0069)が書かせていただきます。
次の日、19日目は[@hmarf](https://qiita.com/hmarf)さんです！楽しみにしてます！
自分は、2019年8月にCATechDojo(Kotlin編)に参加させていただいた後、11月にCATechJOBでマッチングエージェントさんでiOSエンジニアとしてインターンをさせていただきました。大変お世話になりました。

# はじめに 
私は現在進行形で個人アプリの開発をしています。その際に初めは[R.swift](https://github.com/mac-cain13/R.swift)を導入していたのですが、途中から[SwiftGen](https://github.com/SwiftGen/SwiftGen)へ移行したのでそこでわかった、互いの良い面、悪い面について触れていければと思います。

R.swift、SwiftGenの話はこの記事を見たら理解が十分な状態に仕上げられていると思うのでよかったら最後まで読んでいただけるとありがたいです。

# R.swift、SwiftGenとは

これらは主に文字列管理のしやすさのために使用するライブラリです。
文字列管理で一番最初に思い浮かべるのは多言語化対応かと思いますが、それだけではありません。
多言語化対応しないアプリでも導入する価値は十分にあります。`UIImage`や`UIColor`の指定もコードでtypeSafeで利用することができるので利便性があります。`image literal`や`color literal`で指定したものは次に開いたときには何を指定していたかわからなくなるのでそれが気に入らない方はコードで指定している分、管理しやすいとも言えます。

# 結論
結論から言うと[R.swift](https://github.com/mac-cain13/R.swift)と[SwiftGen](https://github.com/SwiftGen/SwiftGen)、どちらを導入するか迷っている方で**少なくともSwiftGenを導入することを大きなコストと捉えていない方はSwiftGenを導入する方がいいのでは**という気持ちです。

その理由も含めて、まずは導入方法から色々と説明していこうと思います。

このようなフローで説明していきます。
(飛ばしたい方はこちらから選択して見て行って下さい)

- [**R.swiftについて**](https://qiita.com/ostk0069/items/aa07ac55be075a50326c#rswift)
    - [導入方法](https://qiita.com/ostk0069/items/aa07ac55be075a50326c#%E5%B0%8E%E5%85%A5%E6%96%B9%E6%B3%95)
    - [記述方法のサンプル](https://qiita.com/ostk0069/items/aa07ac55be075a50326c#%E8%A8%98%E8%BF%B0%E6%96%B9%E6%B3%95%E3%81%AE%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB)

- [**SwiftGenについて**](https://qiita.com/ostk0069/items/aa07ac55be075a50326c#swiftgen)
    - [導入方法](https://qiita.com/ostk0069/items/aa07ac55be075a50326c#%E5%B0%8E%E5%85%A5%E6%96%B9%E6%B3%95-1)
    - [記述方法のサンプル](https://qiita.com/ostk0069/items/aa07ac55be075a50326c#%E8%A8%98%E8%BF%B0%E6%96%B9%E6%B3%95%E3%81%AE%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB-1)

- [**R.swiftとSwiftGenの比較**](https://qiita.com/ostk0069/items/aa07ac55be075a50326c#rswift%E3%81%A8swiftgen%E3%81%AE%E6%AF%94%E8%BC%83)

- [**どっちを選ぶべきなのか**](https://qiita.com/ostk0069/items/aa07ac55be075a50326c#%E3%81%A9%E3%81%A3%E3%81%A1%E3%82%92%E9%81%B8%E3%81%B6%E3%81%B9%E3%81%8D%E3%81%AA%E3%81%AE%E3%81%8B)

本来であれば比較とどっちを選ぶべきであるかのみでもいいかなと思ったのですが、
導入方法について、紹介したいTipsがあったので導入方法も追加することにしました！

# R.swift
## 導入方法
GitHubに記載されている[ドキュメント](https://github.com/mac-cain13/R.swift#installation)を参照すると**CocoaPodsでの導入が推奨されている**のでそちらでの方法で説明していきたいと思います。

ここではCocoaPods自体の導入方法は説明しないのでわからない方は[こちら](https://qiita.com/ShinokiRyosei/items/3090290cb72434852460)からどうぞ。

Podfileへ以下の一行を追加します。

```ruby:Podfile
pod 'R.swift'
```

その後、Terminal上で以下のコマンドを叩きます。

```
pod install
```

この後、[ドキュメント](https://github.com/mac-cain13/R.swift#installation)では`New Run Script Phase`を選択したのち、`"$PODS_ROOT/R.swift/rswift" generate "$SRCROOT/R.generated.swift"`を追加するように書いてあります。

しかし、この記述方法はあまりオススメしません。自動生成されるファイルの`R.generated.swift`が自分のプロジェクトファイルのレポジトリ直下に生成されてしまうからです。GitHubでソースコードを管理している人は特にレポジトリ直下に`.swift`のファイルが存在するのは結構違和感があるのではないかと思います。なので`New Run Script Phase`を選択したのち、以下を追加しましょう。

```sh
"$PODS_ROOT/R.swift/rswift" generate "$SRCROOT/"プロジェクトファイル"/"hoge"/"fuga"/R.generated.swift"
# プロジェクトファイル以下は自分の好きな場所に追加すればいいと思います
```

その後、アプリをビルドすることで`R.generated.swift`のファイルが上記で指定したディレクトリに自動生成されます。**自動生成されたファイルは`.project`への追加は自動でされないので、手動で追加してあげましょう。**

その後、自動生成ファイルをgitで管理する意味はないので

```shell:.gitignore
*.generated.swift
```

を追加してあげることでひとまず導入は終了となります。

## 記述方法のサンプル
これはドキュメントから引っ張ってきたものなんですが、

通常で書く場合

```swift
let icon = UIImage(named: "settings-icon")
let font = UIFont(name: "San Francisco", size: 42)
let color = UIColor(named: "indicator highlight")
let viewController = CustomViewController(nibName: "CustomView", bundle: nil)
let string = String(format: NSLocalizedString("welcome.withName"))
```

`R.swift`で記述した場合

```swift
let icon = R.image.settingsIcon()
let font = R.font.sanFrancisco(size: 42)
let color = R.color.indicatorHighlight()
let viewController = CustomViewController(nib: R.nib.customView)
let string = R.string.localizable.welcomeWithName("Arthur Dent")
```

とこのような違いがあります。すべて`R.`が始まる共通点がありますね。

# SwiftGen
## 導入方法

`R.swift`に対して導入は`SwiftGen`の方が少し複雑です。
まず初めに導入方法を選ぶ必要がある訳ですが、今回は`R.swift`のときにCocoaPodsで導入の説明を行ったので揃えた方が違いを理解しやすいと思うので`CocoaPods`を使った場合での説明を行っていこうと思います。

Podfileへ以下の一行を追加します。

```ruby:Podfile
pod 'SwiftGen'
```

その後、Terminal上で以下のコマンドを叩きます。

```
pod install
```

次に設定ファイルを作成します。このファイルの設定を記述するのがこの導入を難しくしています。

```sh
touch swiftgen.yml
```

作成した`swiftgen.yml`を編集していく訳ですが、まず初めにドキュメントに書かれているサンプルを見ていきましょう

```sh:swiftgen.yml
strings:
  inputs: Resources/Base.lproj
  filter: .+\.strings$
  outputs:
    - templateName: structured-swift5
      output: Generated/strings.swift
xcassets:
  inputs:
    - Resources/Images.xcassets
    - Resources/MoreImages.xcassets
  outputs:
    - templateName: swift5
      output: Generated/assets-images.swift
```

これがどのような設定をしているのか説明していきます。

このサンプルでは

- `Base.lproj`のファイルからの生成
- `.xcassets`のファイルからの生成

を行っています。もちろん、`SwiftGen`ではそれ以外の`Storyboard`とかも生成できる訳ですが、それは別途記述が必要です。

さらに具体的にどのように書いているか見ていくと、
`inputs`と`outputs`が定義されていて

- `inputs`は生成したいファイルの指定
- `outputs`は生成したもの出力先

気になるのは`filter`と`templateName`というものが存在することです。

### filterとは

本来であればinputsの中のファイルの指定は`Base.lproj`ではなく、`Base.lproj/Localizable.strings`である方が自然です。しかし、多言語化対応をしているとおそらく`Localizable.strings`は対応している言語分あるはずなので指定が面倒なので`filter: .+\.strings$`と記述してあげることですべての`Localizable.strings`をinputsの対象に含めることができます

### templateNameとは

`SwiftGen`のなかで指定することのできる`templateName`は以下です。

- `structured-swift5`
- `flat-swift5`
- `swift5`
- `structured-swift4`
- `flat-swift4`
- `swift4`
- `structured-swift3`
- `flat-swift3`
- `swift3`

特に使用しているSwiftのバージョンに揃える形で対応するといいかと思われます。

**`string`の指定をするときには`swift4`や`swift3`の指定は行えないので注意してください。**
`flat`か`structured`を選択するときは構造的にしたいかしたくないかで選びましょう。`string`の場合、多言語化対応が多いと思うので`structured`を推奨します。

この選択肢以外にも自分でよりカスタマイズしたい方は別の方法が存在します。
それは[Stencil](https://github.com/stencilproject/Stencil)を使うことで実現可能です。元々上記の`structured-swift4`や`flat-swift4`も[Stencil](https://github.com/stencilproject/Stencil)を使って作成されたテンプレートです。気になった方はぜひ見てみてください。(この記事ではこの内容にこれ以上Stencilについては触れません)

## 記述方法のサンプル

通常で書く場合

```swift
let icon = UIImage(asset: Asset.Exotic.icon) 
let displayRegular = UIFont(font: FontFamily.SanFrancisco.regular, size: 42.0)
let title = UIColor(named: .articleBody)
let string = String(format: NSLocalizedString("welcome.with_name"))
```

`SwiftGen`で記述した場合

```swift
let icon = Asset.Exotic.banana.image
let displayRegular = FontFamily.SFNSDisplay.regular.font(size: 42.0)
let title = ColorName.articleBody.color
let message = L10n.Welcome.withName
```

## R.swiftとSwiftGenの比較

特徴を並べていきます。

`R.swift`
- **導入が楽**
- **リソースが単一**
- **buildするタイミングで毎回自動生成が走る**

`SwiftGen`
- **`swiftgen.yml`ファイルでカスタマイズできるので拡張性がある**
- **リソースが複数**
- **差分を見て生成するかどうかを見てくれる**

今回この記事を書くきっかけにもなったのですが、私はどちらも週数間の運用をしました。
その中でSwiftGenの方がいいと思ったので途中で乗り換えました
理由としては

- **`UIView`は`R.string`や`SwiftGen`で管理する理由がそこまでないので、網羅性のある`R.swift`の強みが生きなかった**
- **メインで使うlocalizeStringが長いので画面名とか役割とかを付け足すと`R.swift`だと長くなってしまう**
    - `R.swift`だと: `R.string.localizable.hoge`
    - `SwiftGen`だと: `L10n.hoge`
- **設定する側を`R.swift`だとスネークケースで書くとスネークケースのままで生成されてしまうが、`SwiftGen`だとスネークケースで書いたものをキャメルケースで生成してくれる**
- **末尾に()がつくかどうか**
    - コードを書いている時の保管として`()なし`、`（）`、`(Void)`の３つの選択肢が出てくるのでめんどい

の4つが主なものです。

僕は、特に中２つを利用したいがためにSwiftGenを採用しました。特にlocalizeStringの長さの話はすごく重要だと思っています。
良くあるケースとして`{ViewControllerの名前}.{UIViewの名前}.{enumの名前}.title`とかを指定した場合は可読性が下がりやすいのでなるべく呼び出すときのデフォルトのクラス名は少ない方が嬉しいです。
また、スネークケース、キャメルケースの話も、長く運用する上では大事だと思っています。`Localizable.Strings`でキャメルケースを書くのは自分はあまり好みには思えませんでした。
これらは実際にどっちも運用したことがないと気づけない話なので参考になれば嬉しいです。

一番最初に言った、**どちらを導入するか迷っている方でSwiftGenを導入することを大きなコストと捉えていない方はSwiftGenを導入するべき**はこれらが理由です。`R.swift`は導入しやすい分、で細かいところに届きにくい設計になってしまいます。長期的な運用を考えるとそこのリスクはとるのは難しいかもしれません。

逆に言えば、これらをメリットと感じないのであれば`R.swift`を使う方がいいと言えます。

## どっちを選ぶべきなのか

最終的には当たり前ですが、**好きな方を選べばいい**という話に落ち着きます。しかし、この記事を見ている方は長期運用を想定した上で文字列管理をしようとしている方が多いのではないでしょうか。めんどくさい作業が発生するので安易に乗り換えることはできないので将来性を加味して選ぶ必要があると思います(経験談)


# 参考資料
- https://github.com/mac-cain13/R.swift
- https://github.com/SwiftGen/SwiftGen

# 追記
- `structured-swift5`, `flat-swift5`, `swift5`に対応したので記事を編集しました
- **`R.swiftとSwiftGenの比較`での`SwiftGen`の利点としてスネークケースで書けることを上げていますが、Xcode内での検索の観点からキャメルケースで書くことを個人的に強くお勧めします**