LoginSignup
9
7

More than 5 years have passed since last update.

SwiftでのNSInvocation

Last updated at Posted at 2014-10-26

SwiftにはNSInvocationがない。

ないわけじゃあないみたいだが、実際に使おうとすると'NSInvocation' is unavailableというエラーメッセージが出てしまう。

とはいえ、Swiftでは関数をごく自然にデータ型の一つとして使えるので困ることはない。と思っていたのだが……


CocoaフレームワークにはObjCのセレクタの仕組みを使った、デリゲートパターンによる実装が多い。
自分がはまったのはこれ。

objc
- (void)attemptRecoveryFromError:(NSError *)error
                     optionIndex:(NSUInteger)recoveryOptionIndex
                        delegate:(id)delegate
              didRecoverSelector:(SEL)didRecoverSelector
                     contextInfo:(void *)contextInfo

このメソッドは非形式プロトコルで、エラーチェインにこれを実装したオブジェクトを登録すると受け取ったエラーからの回復を試みることができる。エラーからの回復を試した後は、delegatedidRecoverSelectorで指定されたメソッドを呼び出し、回復の成否やコンテキストを渡すことができる。

ARC環境下では-[NSObject performSelector:]が使えないので、NSInvocationを使って呼び出していた。
このコードをSwiftに移植すると、NSInvocationは使えないというエラーになってしまうのである。困った。

初めはNSObject#methodForSelectorを使ってIMP型を取得し、キャストして使おうと思っていたのだが、IMP型はCOpaquePointerとして定義され、どうもSwiftからはその中身に一切触ることができないらしい。なんのためにあるんだろうか?


結局、NSErroruserInfoプロパティにコールバックを格納して使うことにした。しかし、関数を直接Dictionaryには入れられないのでラッパークラスを作るハメに。

Swift
class Function<A, R> {
    let function: A -> R
    init(f: A -> R) {
        function = f
    }
    func call(args: A) -> R {
        return function(args)
    }
}

で、使うときはこういう感じ。

Swift
let f = error.userInfo![key] as Functor<(...), Void>
f.call(...)

今回はNSErrorが任意のオブジェクトを格納できるおかげでなんとか解決できたが、一般的なアプローチではない。誰かいい方法を教えてください。

Swiftのライブラリはまだ未成熟なので、この記事の情報が一刻も早く古くなることを期待しています。

参考:
Mac Developer Library: Error Handling Programming Guide
Swiftの関数の引数は、常に一つ

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7