[iOS][R.swift] 未使用な不要イメージリソースを一括検出

プロジェクトのアップデートを繰り返していると未使用なイメージなどのリソースが放置され、次第にアプリサイズが増大してくることがよくある。
しかしXcodeはそのあたりのケアをしてくれない。膨大なリソース数だったら１つ１つ検索もしたくない。
スクリプトで頑張る文化となっている（と思われる）

不要なイメージを検出するスクリプトは適当に落ちてるけど、プロジェクトの形態や構成がまちまちなためか、

* pngやjpgなど特定のファイルフォーマットのものを対象としている
* .mや.hを対象としたobj-c用
* asset参照名と実体ファイル名が同一の前提

などなかなか自分のプロジェクトにあったものがなかったりする上に
R.swiftなどのリソース管理ライブラリを使用しているとうまく検出できなかったりもする。

そこで次の条件のプロジェクトに最適化した不要リソース検出スクリプトを書いてみた。

* R.swiftでリソース管理している
* イメージはSingle Scale(PDF)で管理している
* ついでにxcassetsで管理している色(.colorset)も不要なものを検出しておきたい

こちらに上げておいた。
https://github.com/yumemi-ajike/detect_unused_resources

* Xcode 11.3.1
* R.swift 5.0.2


```Shell:detect_unused_resources.sh
#!/bin/sh
# Usage:
# ```
# $ cd path/to/project_root
# $ sh path/to/detect_unused_resources.sh . project_name/Assets.xcassets imageset
# ```
# $1=The search path for .xib, .swift, .storyboard files in your project.
# $2=The path of .xcassets directory
# $3=Specify "imageset" or "colorset"

echo "Creating files from $1 ..."
files=(`find $1 -type f -name "R.generated.swift" -prune -o -type f \( -name '*.xib' -o -name '*.swift' -o -name '*.storyboard' \) -exec echo ''{}'' \;`)
echo "${#files[*]} files found."

echo "Creating $3 resources from $2 ..."
resources=(`find $2 -type d -name *.$3 -exec echo ''{}'' \;`)
number_of_resources=${#resources[*]}
echo "$number_of_resources resources found."

i=0
unused_resources=()

for resource in ${resources[*]}
do
	let "i += 1"

	name=`basename -s .$3 $resource`
	if ! grep -qhs "\b$name\b" ${files[*]}; then
		unused_resources+=( $name )
		echo "($i / $number_of_resources) $name is not referenced."
	else
		echo "($i / $number_of_resources)"
	fi
done

echo "${#unused_resources[*]} unused resources found.\n${unused_resources[*]}\n"

```

## 使い方
```
$ cd path/to/project_root
$ sh path/to/detect_unused_resources.sh . project_name/Assets.xcassets imageset
```

引数は以下の通り
１つ目、プロジェクト内の.xib, .swift, .storyboardを検索するパス
２つ目、.xcassetsのパス
３つ目、"imageset"または"colorset"を指定する

## 処理内容
```Shell
echo "Creating files from $1 ..."
files=(`find $1 -type f -name "R.generated.swift" -prune -o -type f \( -name '*.xib' -o -name '*.swift' -o -name '*.storyboard' \) -exec echo ''{}'' \;`)
echo "${#files[*]} files found."
```

１つ目の引数で指定したパスからR.generated.swiftを除外した上で再帰的に.xib, .swift, .storyboardファイルを検索している。

```Shell
echo "Creating $3 resources from $2 ..."
resources=(`find $2 -type d -name *.$3 -exec echo ''{}'' \;`)
number_of_resources=${#resources[*]}
echo "$number_of_resources resources found."
```

２つ目の引数で指定したパスから再帰的に３つ目で指定した拡張子を持つディレクトリを検索している。
例では、Assets.xcassets以下から.imagesetのディレクトリを検索している。

```Shell
for resource in ${resources[*]}
do
	let "i += 1"

	name=`basename -s .$3 $resource`
	if ! grep -qhs "\b$name\b" ${files[*]}; then
		unused_resources+=( $name )
		echo "($i / $number_of_resources) $name is not referenced."
	else
		echo "($i / $number_of_resources)"
	fi
done
```

`files`内から拡張子を除いたディレクトリ名をgrepして、無ければ`unused_resources`に追加している。

```Shell
echo "${#unused_resources[*]} unused resources found.\n${unused_resources[*]}\n"
```

最後に検出したリソース数と内容を出力。

## 結果

```
Creating files from . ...
1106 files found.
Creating imageset resources from project_name/Assets.xcassets ...
76 resources found.
(1 / 76)
(2 / 76)
(3 / 76)
~ 省略 ~
(45 / 76)
(46 / 76) background_image is not referenced.
(47 / 76)
(48 / 76)
(49 / 76) action_menu_icon is not referenced.
(50 / 76) rocket_icon is not referenced.
(51 / 76)
~ 省略 ~
(76 / 76)
3 unused resources found.
background_image action_menu_icon rocket_icon
```

検出された名称を元にXcode上で削除処理を行う必要がある。（ここも自動化したいけど、めんどくさそう。。）

因みに３つ目の引数でcolorsetを指定すると未使用な色定義が検出可能となっている。

```
$ sh path/to/detect_unused_resources.sh . project_name/Assets.xcassets colorset
```

```
Creating files from . ...
1106 files found.
Creating colorset resources from project_name/Assets.xcassets ...
23 resources found.
(1 / 23)
(2 / 23)
(3 / 23)
~ 省略 ~
(17 / 23)
(18 / 23) background_gray is not referenced.
(19 / 23)
(20 / 23) action_menu_red is not referenced.
(21 / 23)
(22 / 23)
(23 / 23)
2 unused resources found.
background_gray action_menu_red
```