SwiftにはNSInvocationがない。
ないわけじゃあないみたいだが、実際に使おうとすると'NSInvocation' is unavailableというエラーメッセージが出てしまう。
とはいえ、Swiftでは関数をごく自然にデータ型の一つとして使えるので困ることはない。と思っていたのだが……
CocoaフレームワークにはObjCのセレクタの仕組みを使った、デリゲートパターンによる実装が多い。
自分がはまったのはこれ。
- (void)attemptRecoveryFromError:(NSError *)error
optionIndex:(NSUInteger)recoveryOptionIndex
delegate:(id)delegate
didRecoverSelector:(SEL)didRecoverSelector
contextInfo:(void *)contextInfo
このメソッドは非形式プロトコルで、エラーチェインにこれを実装したオブジェクトを登録すると受け取ったエラーからの回復を試みることができる。エラーからの回復を試した後は、delegateのdidRecoverSelectorで指定されたメソッドを呼び出し、回復の成否やコンテキストを渡すことができる。
ARC環境下では-[NSObject performSelector:]が使えないので、NSInvocationを使って呼び出していた。
このコードをSwiftに移植すると、NSInvocationは使えないというエラーになってしまうのである。困った。
初めはNSObject#methodForSelectorを使ってIMP型を取得し、キャストして使おうと思っていたのだが、IMP型はCOpaquePointerとして定義され、どうもSwiftからはその中身に一切触ることができないらしい。なんのためにあるんだろうか?
結局、NSErrorのuserInfoプロパティにコールバックを格納して使うことにした。しかし、関数を直接Dictionaryには入れられないのでラッパークラスを作るハメに。
class Function<A, R> {
let function: A -> R
init(f: A -> R) {
function = f
}
func call(args: A) -> R {
return function(args)
}
}
で、使うときはこういう感じ。
let f = error.userInfo![key] as Functor<(...), Void>
f.call(...)
今回はNSErrorが任意のオブジェクトを格納できるおかげでなんとか解決できたが、一般的なアプローチではない。誰かいい方法を教えてください。
Swiftのライブラリはまだ未成熟なので、この記事の情報が一刻も早く古くなることを期待しています。
参考:
Mac Developer Library: Error Handling Programming Guide
Swiftの関数の引数は、常に一つ