iOSアプリ開発でもAndroidのR.javaのような仕組みでリソースに安全にアクセスする


先日、会社の後輩と
「AndroidのXMLをiOSでも共有できればいいのにね」
「iOSってNSLocalizedString使っても文字列引数取るからそこで間違える可能性あるよね」
「iOSでも色の定義を#RRGGBBで指定した方がいい」
なんて話していたのですが「・・・だったらR.javaみたいな仕組みを作れば良いのでは？」と思い
これを実現するツールを作ってみました。
[rdotm(R.m)](https://github.com/ksoichiro/rdotm)というそのままの名前のツールです。

ちょっと分かりにくいかもしれませんが、以下のように、XMLで定義した文字列をObjective-Cのコードからアクセスできるようにします。

![](https://raw.githubusercontent.com/ksoichiro/rdotm/master/testdata/images/demo.gif)

## 前提(Androidアプリデベロッパー以外の方へ)

そもそもR.javaって何？という感じかもしれないので少し説明します。
Androidアプリ開発では「R.java」というファイルが自動生成されます。
これは、XMLで定義したUI部品のIDや文字列等の識別子が定数として定義されたファイルで、Javaのコードからリソースにアクセスするのに使います。

例えばアプリ名を以下のように`strings.xml`というファイルに定義します。

```xml
<resources>
    <string name="app_name">Foo</string>
</resources>
```

すると、Javaコードから以下のように文字列として取得することができます。

```java
String appName = getString(R.string.app_name);
```

## ...で、ツールで何ができるの？

冒頭に述べた通り、Androidと同様にXMLで文字列や色を定義しておくと`NSString`や`UIColor`を返すメソッドを含むソースコードを自動生成してくれます。
利用前後のコードを比較してみます。

### 文字列をメソッドを使って取得できる

#### before

```c:Localizable.strings
"title_top" = "デモ";
```

```objc
NSString *title = NSLocalizedString(@"title_top", nil);
```

#### after

```xml:strings.xml
<string name="title_top">デモ</string>
```

```objc
NSString *title = [R string_title_top];
```

外部化した文字列を文字列で指定しなくて済み、安全にアクセスできるようになりました。
(スペルミスしたら当然コンパイルエラー)

### 色をメソッドを使って取得できる

#### before

```objc
[label setTextColor:[UIColor colorWithRed:0/255.0 green:153/255.0 blue:204/255.0 alpha:153/255.0]];
```

#### after

```xml:colors.xml
<color name="default_text">#990099cc</color>
```

```objc
[label setTextColor:[R color_default_text]];
```

ARGBの値を直接指定しなくて済むようになりました。
(これくらいなら工夫次第で普通にできますが)

### 画像をメソッドを使って取得できる

#### before

```objc
UIImage *logo = [UIImage imageNamed:@"logo"];
```

#### after

```objc
UIImage *logo = [R drawable_logo];
```

画像の名前を文字列で指定しなくて済むようになりました。
ツールでは、指定のディレクトリに含まれる画像ファイルを読み取って、before相当のコードを自動生成しています。
実在する画像しかアクセスできなくなるのがポイントです。

### 伝わりきらない気もしますが・・・

もし少しでも魅力を感じていただけたなら、
できたら実際に取り込んで少しコーディングしてみることをおすすめします。
特に文字列定義に関しては、補完候補で文字列が見えてくるので、たぶん驚いてもらえるんではないかと・・

## 導入のメリット

この仕組みを取り入れるメリットをいくつか挙げてみます。

### 存在しない文字列にアクセスするバグがなくなる

Objective-Cで文字列を外部化するというと、`NSLocalizedString`などを使うことになると思います。
しかし、外部化した文字列には文字列をパラメータとしてアクセスしなくてはなりません。

```objc
NSString *appName = NSLocalizedString(@"app_name", nil);
```

このパラメータが間違っていてもObjective-C的には間違いではないのでコンパイルエラーになりません。
上記の場合、もしapp_nameという文字列が定義されていなければ"app_name"という文字列がそのまま表示されてしまいます。
rdotmを使うと、app_nameというリソースをメソッド名で指定するため、存在しない文字列を指定することはできません(指定したらコンパイルエラー)。

```objc
NSString *appName = [R string_app_name];
```

### 存在しない画像にアクセスするバグがなくなる

既に説明した通り、実際に配置されている画像からメソッドを生成するので間違えることがありません。

### Localizable.stringsを開かなくても中身が分かる

自動生成されたメソッドのコメントに実体の文字列を入れているのでコード補完時等に実際の文字列が見えます。
このため、類似の名前を付けた文字列があっても間違いなく正しい文言を選んでコーディングできると思います。

![スクリーンショット 2014-08-07 23.30.41.png](https://qiita-image-store.s3.amazonaws.com/0/29368/a5ba9e60-9138-350e-8070-439cfba763ad.png "スクリーンショット 2014-08-07 23.30.41.png")

### Androidアプリと並行して開発している場合、文字列や色定義のファイルを共有できる

「同じUIなのにAndroidとiOSで文言が違う！」というありがちな問題をなくせます。
同様に「AndroidとiOSで色コードが微妙に違う」といった問題をなくせます。

## 導入方法

### インストール

ここまで読んでくださって、さらにインストールしようと思ってくださった方は
[GitHubのREADME](https://github.com/ksoichiro/rdotm/blob/master/README.md)を読んでいただけると思いますが・・・

Go言語で書いているため、1つのバイナリファイルを置くだけで導入できるのですが、以降のアップデートができるようにし、複数人での開発時に環境を統一するためにmattnさんの[gom](https://github.com/mattn/gom)で管理すると便利です。

大まかな手順：

1. Go言語インストール
1. go get github.com/mattn/gom
1. プロジェクト内にGomfile作成
1. gom install

### プロジェクトのビルドへの組み込み

対象TARGETの`Build Phase`で`Compile Sources`より前のフェーズに`Run Script Build Phase`を追加し、例えば以下のように記述しておくと、毎回XMLからコード生成した上でビルドするようになります。
(golangとgomを使う場合です)

```sh
export PATH=_vendor/bin:${PATH}
rdotm \
-res ${SRCROOT}/プロジェクト名/XML配置Dir \
-out ${SRCROOT}/プロジェクト名/自動生成先Dir \
-clean
```

## その他細かい補足

* Rじゃないクラス名にもできます。
* 文字列がローカライズしてあっても使えます。
* string_app_nameじゃなくstringAppNameなどにもできます(プレフィクスを指定できます)。

## 最後に

説明が足りない、こんな機能も欲しい、もっといい方法がある、等々、フィードバック歓迎です。
GitHubへのプルリクエストもお待ちしてます。

以上です。