Objective-C でコールバックを持つメソッドを実装する方法について
非同期に結果が返ってくる処理を書く場合、Objective-C では @Protocol を定義して、デリゲートメソッド内で結果をもらうのが良いのかもしれませんが、JavaScript を書いてるとコールバックで結果を取得したいと考えてしまいます。この記事では、Objective-C でコールバック付きのメソッドを作る方法を解説します。(コールバックって呼んでいいのかわかりませんが)
メソッドの書き方 #
サーバーからデータを取得し、そのデータを UIImage として返すメソッドを例とします。以下がメソッドの簡単な書き方です。
// メソッド定義
- (void)requestImageWithURL:(NSURL *)url completion:(void (^)(UIImage *image, NSError *error))completionHandler;
// メソッド実装
- (void)requestImageWithURL:(NSURL *)url completion:(void (^)(UIImage *, NSError *))completionHandler {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
completionHandler(nil, connectionError);
return;
}
UIImage *image = [UIImage imageWithData:data];
completionHandler(image, nil);
}];
}
メソッドの定義 #
定義部分のポイントは completionHandler の書き方です。completionHandler の型に関数のようなものが書いてあります。これは関数のポインタを表しています。void が戻り値を表しており、^ がポインタであることを表しており、UIImage *image, NSError *error が引数を表しています。
このようにメソッドの引数に関数のポインタを渡すことにより、定義したメソッド内で、この関数を実行することができるようになります。この関数をメソッド内の非同期処理が終了したタイミングで呼び出せば、コールバック関数のように使うことができます。
メソッドの実装 #
非同期処理が終了したタイミングで仮引数の completionHandler を実行しています。コールバック関数でメソッドの値を返す時は、コールバック関数の引数に返したい値を与えます。これにより、呼び出し元の関数の、実引数として与えられた関数内でメソッドが返す値にアクセスすることができます。
注意しなければならないのは completionHandler はObjective-C のメソッド呼び出しではなく、C言語の関数呼び出しのように書かなければならないということです。
おわりに #
コールバックの概念は理解しづらいですが、理解できるといろいろと書けるようになるので、書き方は覚えておいて損はないと思います。関数のポインタはまだまだ色々と使い道がありそうです。