IBAction
の命名指針について調べていたところ Why you should not name your @IBActions didTapButton という記事に出合い、参考になりそうな内容が多かったので日本語訳 1 してみました。
なぜ IBAction
に didTapButton
という名前をつけてはいけないのか
私はよく以下のようなコードを目にする:
class MyViewController : UIViewController {
override func viewDidLoad() {
// [...]
let button = UIBarButtonItem(barButtonSystemItem: .cancel,
target: self,
action: #selector(didTapCancelButton(sender:)))
}
@IBAction func didTapCancelButton(sender:Any) {
// cancel
}
}
私はこういうコードが本当に好きじゃない。私が気に入らないのはアクションメソッドの名前、具体的には didTap
の部分だ。 didTapSaveButton
や didHitSend
、 cancelButtonTapped
なんていうのも同じだ。私はこれらに違和感を覚える。そこには主な理由が2つある。
Target-ActionはDelegateパターンではなくCommandパターンである
Guides and Sample Code に隠れているAppleの開発者向けドキュメントの奥深くに、あなたは Model-View-Controller (MVC) パターンについての記事 を見つけられるだろう。そこでは、オリジナルのMVCパターンはCompositeパターン と Strategyパターン 、 Observerパターンから成り立っており、AppleのMVCパターンには左記に加えてMediatorパターンとCommandパターンが使用される、と説明されている。AppleのMVCパターンにおいて、Target-ActionはCommandパターンの部分に相当する。
CommandパターンのTarget-Actionでは、ボタンのようなUI要素は呼び出し側であり、アクション・メッセージが渡されるオブジェクト上のメソッドがコマンドであり、ターゲットがレシーバーだ。 undo
、 deleteBackward
、 lowercaseWord
、 scrollToEndOfDocument
といったものがコマンドだ。それらはすべて NSResponder
が理解し、特定のインスタンスやその他の状況に応じて反応する可能性があるアクションだ。 WKWebView
にも reload
がある。 UIResponder
は小さいがそれでも cut(_:)
、 copy(_:)
、 paste(_:)
、 selectAll(_:)
、 そしてもちろん toggleBoldface(_:)
、 toggleItalics(_:)
、 toggleUnderline(_:)
といったクラシックを備えている。これらから、テキスト選択時のオーバーレイ・メニューがどのように機能するのかが思い浮かんだだろうか?
このようにAppleはアクションをコマンドのように呼んでおり、それに従うのが良さそうだ。それとは対照的に、 didTapXButton
といった名前をつけられたアクションはデリゲートメソッドを想起させる。しかし、私たちはここではDelegateパターンは使わない。私たちは、Delegateパターンを使うときのような、特定の情報を必要とする非常に具体的なソースからの呼び出しは期待していない。その代わりに、解釈・実行するためのコマンドがある。そしてこのコマンドは呼び出されるかもしれないし、呼び出されないかもしれない。あるいは複数回呼び出されることもあるだろう。そして、私たちは何によってそのアクションが呼び出されるかすら知ることはできない。
Target-Actionと呼び出し元には関連性がない
IBAction
はどんな種類のコントロールからも呼び出すことができる。あなたの didTapSendButton
は、ユーザーがメッセージ入力欄にテキストを入力してキーボードのリターン・キーを押したときに呼び出されるかもしれない。macOSアプリでは、そのトリガーがメニュー項目からのメール送信であるかもしれない。またウィンドウではなくTouch Barのボタンをタップしたときかもしれないし、あるいはトリプルタップのジェスチャーかもしれない。何でも構わない。ユーザーの意図は明確で、彼らはただ書いたものを送信したいだけだ。
メソッド名にUI要素の名前を加えると、あなたはコントローラーとビューを必要以上に結びつけてしまうことになる。そしてユーザーがどのようにしてそのアクションを呼び出したのかを知る必要がある。あるユーザーが本当にTouch Barを使っているのかどうかを追跡したい場合などにおいては、 sender
プロパティーを使い、何かしらのカスタマイゼーションを施したりするかもしれない。それは目的にかなっている。しかし、特別な場合にいくつかのカスタマイゼーションを施し、それ以外の場合にはアクションメソッドを通常の状態にしておくことで、あなたが聞いたこともないUI要素からトリガーされたときはるかに柔軟に対応できる。
いくつかの極めて特殊なコマンドを除けば、アクションがどこから呼び出されるかは問題ではない。 lowercaseWord
は「現在選択されている単語を小文字にする」ことを意味する。 copy
は「現在選択中のデータ(テキストや画像など)をクリップボードに保存する」ことを意味する。 selectAll
は「(それが何であろうと)表示されているものすべてを選択する」ことを意味する。そして undo
の意味は「気が変わったのでロールバックしてください」だ。
アクションをコマンドのように命名すべし
そのためAppleは、パターンに適合し、コントローラーをビューから切り離しているこの方法によって、メソッドに名前をつけている。これを好きにならない理由はないだろう。だから我々はアクションをコマンドのように命名するべきだ。
didTapCancelButton(sender:Any)
saveButtonTapped(sender:Any)
didHitSend(sender:Any)
ではなく、
cancel(sender:Any)
save(sender:Any)
sendMessage(sender:Any)
と命名しよう。
ところでInterface Builderを使うかどうかは問題ではない。ターゲットとアクションをUI要素に割り当てるかぎりにおいて、アクションメソッドはこの命名パターンに従うべきだ。私はアクションメソッドの先頭に @IBAction
を加えることもおすすめする。これはアクションメソッドが任意のタイミング・方法によってUI要素から呼び出されることを他のプログラマーに示す良い目印となる。
-
一部不自然な表現や意訳も入っていますので、正確で完全な内容については Why you should not name your @IBActions didTapButton を参照してください ↩