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の関数の引数は、常に一つ