[XCODE] 処理が終わった際のコールバックをSelectorやBlockで実装する方法
こんにちは、@yoheiMuneです。
今日はObjective-Cを用いた実装で、処理が終わった際に呼び出し元を呼び出す方法として、SelectorとBlockの実装をブログに書きたいと思います。
HTTP通信やファイルIOは時間のかかる処理で、その処理をメインスレッドで行う(=同期で処理する)と、UI描画が止まってしまい、残念なアプリになってしまいます。
それに対応するため、HTTP通信やファイルIOはサブスレッドで行い、終わったら呼び出し元に通知するコールバック型のアーキテクチャがいい感じに機能します。
今回のブログでは、コールバックアーキテクチャをSelectorとBlockとそれぞれで行う方法を記載したいと思います。
Selector(=Javaでいうとjava.lang.Methodみたいなやつ)とそのメソッド保持インスタンスをコールバックに指定して、 処理が終わったら呼び出してもらいます。
呼び出し側の実装例は、以下のようになります。
(呼び出し側)
続いて呼び出されるSomeWorkerクラスの実装です。
idと@selectorの受け取り方、呼び出し方がポイントとなります。
(呼び出される側)
インラインコメントにも書きましたが、HTTP通信やファイルダウンロード、ファイル操作、DB操作などをUIに影響させないように非同期で行って、 その結果が分かったらコールバックしてもらうなどに使うと便利な仕組みです。
呼び出し先のメソッドの引数に、Block(処理のまとまり)を渡して、処理が終わり次第そのBlockを呼び出してもらう感じです。
呼び出し側の実装は、以下のような感じになります。。
(呼び出し側)
個人的には、呼び出し処理と完了処理が近くに記述されるので、読みやすいなぁと思ってます。
ただ、Blockの構造はObjective-C特有なので、慣れないとですがw。
続いて呼び出される側の実装です。
Blockは使えない環境もあるようなので、利用できるかを確認するコードも含めて、実装を行います。
(呼び出される側)
Blockの型定義が独特な形をしている点が慣れないのですが、コールバックを呼び出す部分はC言語の関数のように呼び出せるので、すごく奇麗に書けます。
また、Selectorを用いた場合とは異なり、Blockの型定義をしているおかげで、Blockの呼び出し時に異なる型を指定すると、コンパイラからワーニングがでます。
Objective-Cの実装をしていると、言語レベルでもフレームワークレベルでもたくさん学ぶことがあって、ありがたい限りです。 今後も良い内容があればブログに書いていきたいと思います。
最後までご覧頂きましてありがとうございました。
今日はObjective-Cを用いた実装で、処理が終わった際に呼び出し元を呼び出す方法として、SelectorとBlockの実装をブログに書きたいと思います。
コールバックをSelectorやBlockで実装するとは
node.jsやjavaScriptのXHRを用いた実装では一般的なアーキテクチャで、 HTTP通信やファイルIOなどの処理が終わったら、結果を呼び出し元に通知するコールバックの実装をObjective-Cで実装する方法を記載した記事となります。HTTP通信やファイルIOは時間のかかる処理で、その処理をメインスレッドで行う(=同期で処理する)と、UI描画が止まってしまい、残念なアプリになってしまいます。
それに対応するため、HTTP通信やファイルIOはサブスレッドで行い、終わったら呼び出し元に通知するコールバック型のアーキテクチャがいい感じに機能します。
今回のブログでは、コールバックアーキテクチャをSelectorとBlockとそれぞれで行う方法を記載したいと思います。
Selectorを用いてコールバックアーキテクチャを実装する
まずはSelectorを用いたコールバックアーキテクチャです。Selector(=Javaでいうとjava.lang.Methodみたいなやつ)とそのメソッド保持インスタンスをコールバックに指定して、 処理が終わったら呼び出してもらいます。
呼び出し側の実装例は、以下のようになります。
(呼び出し側)
// .hファイル #import <UIKit/UIKit.h> #import "SomeWorker.h" @interface CallbackViewController : UIViewController { // 呼び出すWorkerのインスタンス SomeWorker *someWorker; } // なんらかの処理を行うメソッド。 - (void)callSomeWorker; // コールバックで呼んでもらうメソッド -(void)callbackFprWorkWithResult:(NSDictionary *)resultDictionary error:(NSError *)error; @end // .mファイル #import CallbackViewController @implementation CallbackViewController - (void)callSomeWorker { // Selectorを利用する場合 // doSomeWorkWithの引数に、self(コールバック処理を保有するインスタンス)と // selector(コールバック処理)を指定します。 someWorker = [[SomeWorker alloc] init]; [someWorker doSomeWorkWith:self selector:@selector(callbackFprWorkWithResult:error:)]; } // コールバックで呼んでもらうメソッドを実装します。 -(void)callbackFprWorkWithResult:(NSDictionary *)resultDictionary error:(NSError *)error { NSLog(@"callbackForWorkWithResult:error: is called."); NSLog(@"resultDictonary = %@", resultDictionary); NSLog(@"error = %@", error); } @endこれで呼び出し側の実装が完了です。 @selector(...)という記述が慣れない感じですが、それ以外は普通な感じです。
続いて呼び出されるSomeWorkerクラスの実装です。
idと@selectorの受け取り方、呼び出し方がポイントとなります。
(呼び出される側)
// ヘッダーファイル(.hファイル) @interface SomeWorker : NSObject // idとselectorを引数で受け取るメソッドを定義します。 -(void)doSomeWorkWith:(id)callbackInstance selector:(SEL)callbackSelector; @end // 実装ファイル(.mファイル) #import "SomeWorker.h" @implementation SomeWorker // 以下のpragma...は、ARC利用時に出るワーニングを抑止するもの。 #pragma clang diagnostic ignored "-Warc-performSelector-leaks" -(void)doSomeWorkWith:(id)callbackInstance selector:(SEL)callbackSelector { // 何か処理を行う。HTTP通信やファイル扱いなど。 NSError *error = nil; NSMutableDictionary *dictonary = /*なんらかの処理結果を受け取る*/; // コールバックが指定されている場合には、コールバックを行う。 if (callbackInstance && callbackSelector) { NSMethodSignature *sig = [callbackInstance methodSignatureForSelector:callbackSelector]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; [invocation setSelector:callbackSelector]; [invocation setTarget:callbackInstance]; // コールバックの引数に値を指定する場合には、index >=2を指定する。 [invocation setArgument:&dictonary atIndex:2]; [invocation setArgument:&error atIndex:3]; [invocation invoke]; } // callback. // 引数が2つまでの場合には、以下のように簡単にも書ける。 // if (callbackInstance && callbackSelector) { // [callbackInstance performSelector:callbackSelector withObject:dictonary withObject:error]; // } } @endこんな感じで処理を記述することで、idとselectorを利用してコールバック処理を記述することが出来ます。
インラインコメントにも書きましたが、HTTP通信やファイルダウンロード、ファイル操作、DB操作などをUIに影響させないように非同期で行って、 その結果が分かったらコールバックしてもらうなどに使うと便利な仕組みです。
Blockを用いたコールバックを実装する
続いてBlockを用いたコールバック処理の実装方法です。呼び出し先のメソッドの引数に、Block(処理のまとまり)を渡して、処理が終わり次第そのBlockを呼び出してもらう感じです。
呼び出し側の実装は、以下のような感じになります。。
(呼び出し側)
// ヘッダファイル(.hファイル) #import <UIKit/UIKit.h> #import "SomeWorker.h" @interface CallbackViewController : UIViewController { SomeWorker *someWorker; } // なんらかの処理を行うメソッド。 - (void)callSomeWorker; @end // 実装ファイル(.mファイル) #import CallbackViewController @implementation CallbackViewController - (void)callSomeWorker { // Selectorを利用する場合 // doSomeWorkWithの引数に、self(コールバック処理を保有するインスタンス)と // selector(コールバック処理)を指定します。 someWorker = [[SomeWorker alloc] init]; [someWorker doSomeWorkWith:^(NSDictionary *resultDictonary, NSError *error) { // ここの中がコールバック処理。 // 引数で結果(resultDictionary)とエラー内容(error)を受け取る。 NSLog(@"CallbackHandler called."); NSLog(@"resultDictonary = %@", resultDictonary); NSLog(@"error = %@", error); }]; } @endBlockで実装した場合には、コールバック処理用のメソッドをわざわざ準備しなくていいので、楽チンです。
個人的には、呼び出し処理と完了処理が近くに記述されるので、読みやすいなぁと思ってます。
ただ、Blockの構造はObjective-C特有なので、慣れないとですがw。
続いて呼び出される側の実装です。
Blockは使えない環境もあるようなので、利用できるかを確認するコードも含めて、実装を行います。
(呼び出される側)
// ヘッダファイル(.hファイル) // NS_BLOCKS_AVAILABLEで、Blockが利用可能か否かを判断できます。 // 利用可能な場合のみ、今回利用するコールバック関数用の型を定義します。 #if NS_BLOCKS_AVAILABLE // 型定義をすると、Blockの長ったらしい型名を毎回書く手間を簡略化することが出来ます。 // 型定義では引数の変数名まで指定すると、コールバックを呼び出す側の実装で、コード保管が効いて便利です。 typedef void (^CallbackHandler)(NSDictionary * resultDictonary, NSError *error); #endif @interface SomeWorker : NSObject #if NS_BLOCKS_AVAILABLE // 引数にBlockのコールバック関数を受け取る処理を定義します。 -(void)doSomeWorkWith:(CallbackHandler)handler; #endif @end // 実装ファイル(.mファイル) #import "SomeWorker.h" @implementation SomeWorker // 引数にBlockのコールバック関数を受け取る処理 -(void)doSomeWorkWith:(CallbackHandler)handler { // 何らかの処理を行います。 // ここではコールバックに渡す内容のインスタンスを作成しているのみ。 NSError *error = nil; NSMutableDictionary *dictonary = [NSMutableDictionary dictionary]; // コールバックが指定された場合には、それを呼び出す。 if (handler) { handler(dictonary, error); } } @endこんな感じでBlockを用いたコールバック型の処理を記述することが出来ます。
Blockの型定義が独特な形をしている点が慣れないのですが、コールバックを呼び出す部分はC言語の関数のように呼び出せるので、すごく奇麗に書けます。
また、Selectorを用いた場合とは異なり、Blockの型定義をしているおかげで、Blockの呼び出し時に異なる型を指定すると、コンパイラからワーニングがでます。
最後に
自分は最初は上記2つのやり方を知らずに実装していたので大変な感じでしたが、コールバック方法がわかって奇麗に書けるようになってきました。Objective-Cの実装をしていると、言語レベルでもフレームワークレベルでもたくさん学ぶことがあって、ありがたい限りです。 今後も良い内容があればブログに書いていきたいと思います。
最後までご覧頂きましてありがとうございました。