ある日の昼下がり、ランチ後の眠たい時間帯にプログラミングをしていた時のことである。
self.closeButton.setImage(UIImage(named: "ColseButton"), for: .normal)
あれ、ボタンが表示されない。。。
_人人人人人人_
> ColseButton <
 ̄Y^Y^Y^Y^Y^Y^ ̄
ほむらちゃん、ごめんね。私、魔法少女になる。私、やっとわかったの。叶えたい願いごと見つけたの。
import UIKit
extension UIImage {
class var closeButton: UIImage? {
return UIImage(named: "CloseButton")
}
}
これで、
self.closeButton.setImage(UIImage.closeButton, for: .normal)
こうじゃろ?
命拾いしたわね、貴女達。目に焼き付けておきなさい。魔法少女になるって、そういうことよ。
いやちょっと待て、プロッジェクト内の画像ファイル全てに対して定義するのか?
誰のためでもない、自分自身の祈りのために、戦い続けるのよ。
やだ…、グリーフシードが黒く濁ってきちゃった…。
リソース周りにおけるハードコーディング問題
ここで改めてiOSアプリ開発にけるハードコーディングの問題点。
-
コード補完が効かない
typoの最たる要因。画像ファイル名を変更したり、画像ファイルを削除した際にはさらなる試練が待ち受ける。ミスった場合はグリーフシードが濁る。 -
Interface Builderの仕様上、文字列指定による記述が増えてきた
UINibやUIStoryboardからビューをロードする時、UITableViewやUICollectionViewのセルを取得する時、さらにはSegueを利用する時など、Interface Builder上でIdentifierを設定し、コードでは文字列指定によってこれらを記述する。設定したIdentifierの数だけtypoの可能性が多くなり、Identifierの管理も難しくなる。Identifierが増えれば増えるほどグリーフシードが黒くなる。 -
コンパイラでチェックができない
typoやいつのまにかいなくなってしまった文字列指定の記述が残っていた場合、クラッシュするまで気づくことができない。気づくことなくアプリがリリースされてしまった場合、グリーフシードは真っ黒になり魔女へと変化する。
いくつか挙げてみたが、およそリソース周りにこの問題が集中してる。extensionやenumを利用して定義することである程度改善されるが、ここはなんとか自動化したい。
あるよ。奇跡も、魔法も、あるんだよ。
R.swift
"R.swift"は、画像ファイルやStoryboard、さらにはLocalizable.stringsなどのリソースが、プロパティを参照するように使えるようになるライブラリである。プロジェクトのビルド時にプロジェクト内にある対象となる全てのリソースからコードが自動生成されることで、上記のような機能を実現しているので、一度導入してしまえば、画像ファイルを追加したり削除したりしても特に意識することなく使用することができる。
**"R.swift"の他にも、同様の機能を実現してくれる"SwiftGen"といったライブラリもあるので、気になった方は試してほしい。筆者は記述の仕方が気に入った"R.swift"**を採用した。
R.swiftを導入すると…
self.closeButton.setImage(UIImage(named: "ColseButton"), for: .normal)
コレが…、
self.closeButton.setImage(R.image.closeButton(), for: .normal)
こうなる!
マミさん、あたしの願い、叶ったよ。。。後悔なんて、あるわけない。あたし、今最高に幸せだよ!
Storyboardは…
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as? ViewController
// ↓ こうなる
let viewController = R.storyboard.main.viewController()
Cellは…
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
// ↓ こうなる
let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.tableViewCell, for: indexPath)!
さらにLocalizable.stringsは…
let hoge = NSLocalizedString("hoge", comment: "")
// ↓ こうなる
let hoge = R.string.localizable.hoge()
すごくAndroidです
Rクラスのプロパティとして参照しているので、リソースがなくなった場合にコード内に記述が残っていた際には、当然コンパイル時にエラーになる。
やったね!コレで魔女にならずにすんだよ!
おまけ
UserDefaultsのキーも文字列指定で記述することになると思うのだが、こちらもなんとかしたい。
"SwiftyUserDefaults" なんてものもあるが、ここまでする必要があるか?と感じたので、
import Foundation
extension UserDefaults {
enum Key: String {
case IsSignIn
case IsIntroductionShown
}
}
こんな感じで、
UserDefaults.standard.bool(forKey: UserDefaults.Key.IsIntroductionShown.rawValue)
というようにextensionとenumを利用していい感じに。必要があったら**"SwiftyUserDefaults"**も使ってみよう。