TwitterをObjective-Cから使う11(MGTwitterEngineで同期通信する)

MGTwitterEngineを使っていて、同期通信を行いたい場面がでてきました。NSURLConnectionでsendSynchronousRequestで同期通信で送信するものを、最初に作りましたがタイムアウトの設定ができない、途中でキャンセルできないなどの問題がありました。
そのため、通信部分は非同期通信を使う。しかし、呼び出したメソッドは通信が終わるまで、returnを返さないようにMGTwitterEngineを改造することにします。


MGTwitterEngineのメソッドを呼び出すと、その中でレスポンスが得られるまで待つという風に実装します。
まんず、待たせるという動作を制御する変数をdidFinish_として定義します。これをNOに設定すると"待て"、YESにすると"終われ"という制御を外部から行えるように、プロパティ化しておきます。
MGTwitterEngine.h

@interface MGTwitterEngine : NSObject <MGTwitterParserDelegate>
{
	BOOL didFinish_;
}

@property ( nonatomic ) BOOL didFinish;



フラグの準備ができたら、実際の同期メソッドを実装していきます。


以下、いろいろなtwitter APIを呼び出すメソッドがあるのですが、今回はgetUserTimelineForを例に改造を説明します。
非同期通信を行うgetUserTimelineForは、こんな感じです。受け取った引数をもとにリクエストを組み立て、MGTwitterHTTPURLConnection(NSURLConnection)に渡す処理をしています。MGTwitterHTTPURLConnectionにリクエストを渡した後、レスポンス受信前に呼び元に制御が戻ります。
そして結果は、statusesReceived、requestFailedなどのdelegateメソッドで非同期に受け取ることになります。
MGTwitterEngine.m

@synthesize didFinish = didFinish_;

- (NSString *)getUserTimelineFor:(NSString *)userID count:(int)count
{
	NSString *path = [NSString stringWithFormat:@"statuses/user_timeline.%@", API_FORMAT];
	MGTwitterRequestType requestType = MGTwitterUserTimelineRequest;

	NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
	if (count > 0) {
		[params setObject:[NSString stringWithFormat:@"%d", count] forKey:@"count"];
	}
	if (userID) {
		path = [NSString stringWithFormat:@"statuses/user_timeline/%@.%@", userID, API_FORMAT];
		requestType = MGTwitterUserTimelineForUserRequest;
	}

	return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
							requestType:requestType
						   responseType:MGTwitterStatuses];
}

これを同期通信にしたい訳ですが、非同期通信のメソッドはそのまま使えるように手を加えないようにします。
そのため新たに同期用のメソッドgetSyncUserTimelineForを作成します。実装は、非同期のgetUserTimelineForを呼ぶだけ、そしてフラグが立つまで待つという形にします。
ここで注意点ですが、単純にsleepを入れてループしてしまうと、NSURLConnectionも止まってしまいます。そのためレスポンスを受けれません。NSRunLoopを使い、NSURLConnectionのイベントを処理させます。(run loopは、タイマーとかキー操作とかのイベントを監視して、イベントに応じたイベントハンドラを実行するために回すループです。)

- (NSString *)getSyncUserTimelineFor:(NSString *)userID count:(int)count
{
	didFinish_ = NO;
	NSString* identifier = [self getUserTimelineFor:userID count:count];
	// 外部からの中断以外は、無限ループする。
	while (didFinish_ == NO) {
		// NSConnectionのイベントを処理させる。
		// runUntilDateで0秒後までrun loopを回す。= 1回まわすと同じと考えます。
		[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.0]];
		// 0.1秒間隔
		[NSThread sleepForTimeInterval:0.1];
	}
	return identifier;
}

待っている間に、MGTwitterHTTPURLConnectionがバックグラウンドで処理されていきます。そして、MGTwitterHTTPURLConnectionが失敗もしくは成功しレスポンスを受け取った時に、requestFailed、statusesReceivedのdelegateメソッドが呼ばれます。
delegateメソッドが呼ばれたら、そのタイミングでMGTwitterEngineのメンバ変数にdidFinish_にYESを設定してあげます。すると同期用に作成したメソッドのwhile文の条件が変わり、ループを抜けて呼びもとに制御が戻ります。

- (void)main
{
	// 呼び出しサンプル
	twitterEngine_ = [[MGTwitterEngine alloc] initWithDelegate:self];
	[twitterEngine_ getSyncUserTimelineFor:@"xxxx" count:100];
	[twitterEngine_ release];
}

- (void)requestFailed:(NSString *)connectionIdentifier withError:(NSError *)error
{
	NSLog(@"requestFailed %@",error);
	int statusCode = [error code];
	if (statusCode < 0) {
		// -1001 タイムアウト
		// -1009 オフライン(接続失敗)

	} else {

	}
	twitterEngine_.didFinish = YES;
}


- (void)statusesReceived:(NSArray *)statuses forRequest:(NSString *)connectionIdentifier
{
	NSLog(@"Got statuses for %@:\r%@", connectionIdentifier, statuses);
	twitterEngine_.didFinish = YES;
}

非同期通信と同期通信が両方とも使え、かつ修正量も少なく実装できたと思います、いかがでしょうか。
例のごとく、机上で組んでますので間違えてたらごめんなさい。