bbs
oopers.com
5176 OOPer: 2014-07-15 22:20:16
Swiftいろいろ: [5] idとrespondsToSelector:
Objective-Cのプログラムでは、あるオブジェクトがメソッドを実装しているかどうかを動的にrespondsToSelector:メソッドで確認する場合がよくある。

if( [anObject respondsToSelector:@selector(aMethod)] ) {
  [anObject aMethod];
}


Swiftの場合でも、NSObjectを継承していれば、respondsToSelector()が使えない訳ではないが、あまりSwiftっぽい書き方ではない、ってことのようだ。由緒正しいSwift流の書き方は、optional chainingを使うってもの。

let obj: AnyObject = anObject
obj.aMethod?()


AnyObjectは、Objective-Cでのidに相当するので、どんなメソッドでも呼べる、ついでに、どんなメソッドでも@optional扱いできる、って特典がある。(普通は@optionalじゃないメソッドに?付けるとエラーになる。)んが、実際には、上のようなコードを実行(コンパイル)しようとすると、「AnyObject型にはaMethodなんてメソッドはありません」なんて意味のエラーになってしまう場合がある。
 どうも、AnyObject型から呼び出せるメソッドというのは、importしているmodule内(自アプリを含む)のどっかのクラスかプロトコルで宣言されているものに限るらしい。(Objective-Cのidは、どうだったっけか?)

こういう場合は、自分で呼びたいメソッドをプロトコル宣言(そのプロトコルを実装するクラスは作らなくてもいい)してやると良い。

@objc protocol Optionals {
  func aMethod()
}
//@optionalを付ける必要はないが、(beta3のSwiftでは)@objcがないと、うまくいかなかった。


もちろん、プロトコルの宣言は適切な位置に。

データ型がわかっているオブジェクトをわざわざAnyObject型の変数(ってか、let使ってたら定数というべきなのか?)に代入するなんてのが耐えられないって場合には、上記の方法は使えない。respondsToSelector()メソッド使うしかなかろう。ちなみにSwiftの文字列定数は、自動的にSelector型に変換可能なんで、
if anObject.respondsToSelector(Selector("aMethod")) {
  anObject.aMethod()
}

なんて書かずに、
if anObject.respondsToSelector("aMethod") {
  anObject.aMethod()
}

で済む。
 古いObjective-CのAPIでは、(ブロックではなく)セレクタを引数にとるものが多いが、引数のデータ型がSelectorとわかっている場合も、文字列定数で大丈夫。

ま、これから作るアプリではちゃんとprotocol中で@optional宣言して、そのプロトコルを実装したクラスで、optional chainingするってのが一番Swiftらしいんだろう。@objc付けないと@optionalも使えないってのは美しくないが。