[iOS] gulp の watch 機能を使って R.swift の生成処理を自動化する

2016.08.12

R.swift をもっと簡単にする

先日、弊社の iOS エンジニアの田中が iOS アプリのリソースアクセスを簡単にするライブラリ「R.swift」を紹介いたしました。

【Swift】R.swiftが優秀すぎるので紹介してみる

ここでも改めて触れますが、R.swift では以下のようなことができます。

  • コード補完でリソースファイルにアクセスすることができる
  • リソースファイル取得時に型も補完してくれる
  • 文字列指定による動的な指定をしなくてよいので静的に解析することができる(コンパイル時にエラーを出してくれる)

大変便利なライブラリですが、注意点としてこのライブラリが生成する R.generated.swift を都度生成する必要があります。Run Script に R.swift のコマンドを記述することで生成されますが、リソースファイルを編集したら一度ビルドを実行しなければ R.generated.swift に反映されませんので、ちょっと面倒です。

そこで、直感的に思い浮かんだのが JavaScript 製タスクランナー「gulp」の watch 機能です。こちらを使って、リソースファイルを監視し、変更があったら R.generated.swift を生成するようにしてみました。

今回作成したものは GitHub で公開しております。必要に応じてご参照ください。

suwa-yuki/R.swift-autogen

gulp のインストール

はじめに gulp をインストールします。gulp は Node.js 実行環境が必要となります。Node.js がインストールされていない場合は次のブログを参考にインストールしておきましょう。現在では v4.4.7 が LTS の最新バージョンです。

nodebrewを使ってNode.jsの複数バージョンを簡単に使い分ける

まず gulp コマンドが叩けるよう gulp-cli をグローバルにインストールします。

$ npm install --global gulp-cli

次にプロジェクトのルートディレクトリに、package.json を作成します。npm init から行っても結構です。

package.json

{
  "name": "xcode-watcher",
  "version": "1.0.0",
  "description": "",
  "main": "gulpfile.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "suwa.yuki",
  "license": "ISC",
  "devDependencies": {
    "gulp": "^3.9.1"
  }
}

npm install を実行します。devDependenciesgulp を指定しているので npm install を実行するだけで Gulp がインストールされます。

$ npm install

これで gulp が使えるようになりました。

$ gulp -v
[11:58:23] CLI version 1.2.2
[11:58:23] Local version 3.9.1

R.swift のコマンドをコマンドラインから実行する

次に、Run Script に書いていた R.swift の生成コマンドを、コマンドラインで叩けるようにします。Xcode のビルド時に実行される Run Script は、Xcode が設定する環境変数が使えるようになっており、R.swift はこの一部の情報を使ってコマンドを実行します。そのため、コマンドラインから叩くには Xcode が設定する環境変数を取得する必要があります。そのようなシェルスクリプトを作りました。次のシェルスクリプトファイルを gen.sh という名前で作成してください。

gen.sh

#!/bin/sh
TARGET_NAME=$1
XCODE_SETTINGS="${TARGET_NAME}.settings"

xcodebuild -showBuildSettings -target $TARGET_NAME > ${XCODE_SETTINGS}
xcode_setting() {
    echo $(cat ${XCODE_SETTINGS} | awk "\$1 == \"${1}\" { print \$3 }")
}

export PODS_ROOT=$(xcode_setting "PODS_ROOT")
export PROJECT_FILE_PATH=$(xcode_setting "PROJECT_FILE_PATH")
export TARGET_NAME=$(xcode_setting "TARGET_NAME")
export PRODUCT_BUNDLE_IDENTIFIER=$(xcode_setting "PRODUCT_BUNDLE_IDENTIFIER")
export PRODUCT_MODULE_NAME=$(xcode_setting "PRODUCT_MODULE_NAME")
export BUILT_PRODUCTS_DIR=$(xcode_setting "BUILT_PRODUCTS_DIR")
export DEVELOPER_DIR=$(xcode_setting "DEVELOPER_DIR")
export SOURCE_ROOT=$(xcode_setting "SOURCE_ROOT")
export SRCROOT=$(xcode_setting "SRCROOT")
export SDKROOT=$(xcode_setting "SDKROOT")

${PODS_ROOT}/R.swift/rswift ${TARGET_NAME}

シェルスクリプトファイルを作成したら、実行権限を与えつつ実行してみましょう。R.generated.swift ファイルが生成されるはずです。

$ chmod 755 ./gen.sh
$ gen.sh <ターゲット名>

リソースファイルを監視する

最後に、gulp の watch 機能と R.swift の生成処理を組み合わせます。gulpfile.js を新たに作成します。次のようにします。

gulpfile.js

var gulp = require('gulp'),
    exec = require('child_process').exec;

var target = '<ターゲット名>';

gulp.task('gen', function(cb) {
  exec(`./gen.sh ${target}`, function (err, stdout, stderr) {
    cb(err);
  });
});

gulp.task("watch", function() {
  var targets = [
    './**/*.storyboard',
    './**/*.xib',
    './**/*.json',
    './**/*.png'
  ];
  gulp.watch(targets, ['gen']);
});

watch タスクの中で定義している targets の配列に、監視対象のファイルを指定します。ここでは私がよく使う拡張子だけ明記しています。また target は Xcode プロジェクトのターゲット名を入れてください。

実行してみる

それでは実行してみます。まずはコマンドラインにて gulp watch を実行します。

$ gulp watch
[12:15:38] Using gulpfile ~/Desktop/ResourceSample/gulpfile.js
[12:15:38] Starting 'watch'...
[12:15:38] Finished 'watch' after 147 ms

プロセスが開始し、監視されている状態になりました。この状態で Storyboard ファイルなどを編集したり、画像を追加したりすると R.swift の生成処理が自動で走ります。

まとめ

これで、ようやく Android 感が出てきましたね!(爆)

Xcode プロジェクトの環境変数を引っ張ってくる部分は R.swift に限らず色々な場面で有用だと思うので、ぜひ参考にしてください。

参考