CA Tech Dojo/Challenge/JOB Advent Calendar 2019の18日目は@ostk0069が書かせていただきます。
次の日、19日目は@hmarfさんです!楽しみにしてます!
自分は、2019年8月にCATechDojo(Kotlin編)に参加させていただいた後、11月にCATechJOBでマッチングエージェントさんでiOSエンジニアとしてインターンをさせていただきました。大変お世話になりました。
はじめに
私は現在進行形で個人アプリの開発をしています。その際に初めはR.swiftを導入していたのですが、途中からSwiftGenへ移行したのでそこでわかった、互いの良い面、悪い面について触れていければと思います。
R.swift、SwiftGenの話はこの記事を見たら理解が十分な状態に仕上げられていると思うのでよかったら最後まで読んでいただけるとありがたいです。
R.swift、SwiftGenとは
これらは主に文字列管理のしやすさのために使用するライブラリです。
文字列管理で一番最初に思い浮かべるのは多言語化対応かと思いますが、それだけではありません。
多言語化対応しないアプリでも導入する価値は十分にあります。UIImage
やUIColor
の指定もコードでtypeSafeで利用することができるので利便性があります。image literal
やcolor literal
で指定したものは次に開いたときには何を指定していたかわからなくなるのでそれが気に入らない方はコードで指定している分、管理しやすいとも言えます。
結論
結論から言うとR.swiftとSwiftGen、どちらを導入するか迷っている方で少なくともSwiftGenを導入することを大きなコストと捉えていない方はSwiftGenを導入する方がいいのではという気持ちです。
その理由も含めて、まずは導入方法から色々と説明していこうと思います。
このようなフローで説明していきます。
(飛ばしたい方はこちらから選択して見て行って下さい)
本来であれば比較とどっちを選ぶべきであるかのみでもいいかなと思ったのですが、
導入方法について、紹介したいTipsがあったので導入方法も追加することにしました!
R.swift
導入方法
GitHubに記載されているドキュメントを参照するとCocoaPodsでの導入が推奨されているのでそちらでの方法で説明していきたいと思います。
ここではCocoaPods自体の導入方法は説明しないのでわからない方はこちらからどうぞ。
Podfileへ以下の一行を追加します。
pod 'R.swift'
その後、Terminal上で以下のコマンドを叩きます。
pod install
この後、ドキュメントではNew Run Script Phase
を選択したのち、"$PODS_ROOT/R.swift/rswift" generate "$SRCROOT/R.generated.swift"
を追加するように書いてあります。
しかし、この記述方法はあまりオススメしません。自動生成されるファイルのR.generated.swift
が自分のプロジェクトファイルのレポジトリ直下に生成されてしまうからです。GitHubでソースコードを管理している人は特にレポジトリ直下に.swift
のファイルが存在するのは結構違和感があるのではないかと思います。なのでNew Run Script Phase
を選択したのち、以下を追加しましょう。
"$PODS_ROOT/R.swift/rswift" generate "$SRCROOT/"プロジェクトファイル"/"hoge"/"fuga"/R.generated.swift"
# プロジェクトファイル以下は自分の好きな場所に追加すればいいと思います
その後、アプリをビルドすることでR.generated.swift
のファイルが上記で指定したディレクトリに自動生成されます。自動生成されたファイルは.project
への追加は自動でされないので、手動で追加してあげましょう。
その後、自動生成ファイルをgitで管理する意味はないので
*.generated.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
で記述した場合
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へ以下の一行を追加します。
pod 'SwiftGen'
その後、Terminal上で以下のコマンドを叩きます。
pod install
次に設定ファイルを作成します。このファイルの設定を記述するのがこの導入を難しくしています。
touch swiftgen.yml
作成した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を使うことで実現可能です。元々上記のstructured-swift4
やflat-swift4
もStencilを使って作成されたテンプレートです。気になった方はぜひ見てみてください。(この記事ではこの内容にこれ以上Stencilについては触れません)
記述方法のサンプル
通常で書く場合
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
で記述した場合
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)
の3つの選択肢が出てくるのでめんどい
- コードを書いている時の保管として
の4つが主なものです。
僕は、特に中2つを利用したいがためにSwiftGenを採用しました。特にlocalizeStringの長さの話はすごく重要だと思っています。
良くあるケースとして{ViewControllerの名前}.{UIViewの名前}.{enumの名前}.title
とかを指定した場合は可読性が下がりやすいのでなるべく呼び出すときのデフォルトのクラス名は少ない方が嬉しいです。
また、スネークケース、キャメルケースの話も、長く運用する上では大事だと思っています。Localizable.Strings
でキャメルケースを書くのは自分はあまり好みには思えませんでした。
これらは実際にどっちも運用したことがないと気づけない話なので参考になれば嬉しいです。
一番最初に言った、どちらを導入するか迷っている方でSwiftGenを導入することを大きなコストと捉えていない方はSwiftGenを導入するべきはこれらが理由です。R.swift
は導入しやすい分、で細かいところに届きにくい設計になってしまいます。長期的な運用を考えるとそこのリスクはとるのは難しいかもしれません。
逆に言えば、これらをメリットと感じないのであればR.swift
を使う方がいいと言えます。
どっちを選ぶべきなのか
最終的には当たり前ですが、好きな方を選べばいいという話に落ち着きます。しかし、この記事を見ている方は長期運用を想定した上で文字列管理をしようとしている方が多いのではないでしょうか。めんどくさい作業が発生するので安易に乗り換えることはできないので将来性を加味して選ぶ必要があると思います(経験談)
参考資料
追記
-
structured-swift5
,flat-swift5
,swift5
に対応したので記事を編集しました R.swiftとSwiftGenの比較
でのSwiftGen
の利点としてスネークケースで書けることを上げていますが、Xcode内での検索の観点からキャメルケースで書くことを個人的に強くお勧めします