ブロックを使ってHTTP通信

OS X 10.6, iOS 4.0から使えるようになったブロック構文。iOS 3系のデバイス(特にiPad)のことがあったため、なかなか本格的に使う機会が訪れなかったのですが、先日iPadもOSが4.2となりブロックが使えるようになり、これでようやくブロック構文を本格的に使って実装を行っていく事ができそうです。

ブロックを使うメリット

詳解Objective-C 2.0(改訂版)には「ブロックオブジェクトを用いることで、関数を呼び出す箇所で行いたいと考えていることを、まさにその位置に、そこにある情報を利用して記述することができる(P.331)」と書かれています。
例えば、通常非同期処理を実装するときは処理が完了したときに実行したいコードをコールバックメソッドという形で実装します。この方法で実装すると、非同期処理を呼び出しコードと結果をうけて実行されるコードがソースコードの中で別々の所に記述されることになります。ソースコードが長くなったり、コールバックメソッドの種類が増えてくると、この方法による実装では一つ一つの処理の流れを追っていきにくくなります。
ブロックを使うと、今まで呼び出しと結果をうけて実行されるコードを別々の個所に記述していたものを、一つの流れとして記述することができます。

ブロックを使うデメリット

変数の扱いが複雑です。詳細は「詳解Objective-C 2.0(改訂版)」14章を参照してもらうとして、注意して使わないとバグ/混乱の元になります。非同期処理、マルチスレッドで処理を行うような場合はとくに注意が必要です。

ブロックを使ってHTTP通信を書く

ブロックを使うとプログラムをわかりやすく記述できるケースは沢山あると思いますが、個人的にはHTTP通信部分こそブロック向きじゃないかなと思って調べていたら、既にブロックを使って非同期でHTTP通信を実装している方がいました。
Logic High Blog – Blocks Rock – A Cocoa Asynchronous NSURLConnection block example
このままでも十分に便利ですが、これをベースに少し使いやすいように変更してみました。
変更したい内容としては、

  1. iOSアプリでは、メインスレッドで処理を行わなければ行けない場合が多いので、結果のブロックはメインスレッドで実行させたい。(その分メインスレッドの動きは遅くなりますが)
  2. 結果は文字列ですぐに扱いたい

です。

MyHTTPDownloader.h

//
//  MyHTTPDownloader.h
//
//  Created by Sota Yokoe on 11/01/10.
//  Copyright 2011. All rights reserved.
//  http://nantekottai.com/
//
//  cf. Blocks Rock – A Cocoa Asynchronous NSURLConnection block example
//  http://blog.logichigh.com/2010/09/12/cocoa-blocks/
#import 
@interface MyHTTPDownloader : NSObject {
}
+ (void)asyncRequest:(NSString*)urlString success:(void(^)(NSString*))successBlock
			 failure:(void(^)(NSError*))failureBlock;
@end

MyHTTPDownloader.m

//
//  MyHTTPDownloader.m
//
//  Created by Sota Yokoe on 11/01/10.
//  Copyright 2011. All rights reserved.
//  http://nantekottai.com/
//
//  cf. Blocks Rock – A Cocoa Asynchronous NSURLConnection block example
//  http://blog.logichigh.com/2010/09/12/cocoa-blocks/
#import "MyHTTPDownloader.h"
@implementation MyHTTPDownloader
+ (void)asyncRequest:(NSString*)urlString success:(void(^)(NSString*))successBlock
			 failure:(void(^)(NSError*))failureBlock
{
	[NSThread detachNewThreadSelector:@selector(downloadInBackground:) toTarget:[self class]
						   withObject:[NSDictionary dictionaryWithObjectsAndKeys:urlString, @"urlString",
									   Block_copy(successBlock), @"successBlock",
									   Block_copy(failureBlock), @"failureBlock", nil]];
}
+ (void)downloadInBackground:(NSDictionary*)params
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	NSString* urlString = [params objectForKey:@"urlString"];
	NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
	NSURLResponse *response = nil;
    NSError *error = nil;
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
	NSMutableDictionary* resultDictionary = [NSMutableDictionary dictionaryWithDictionary:params];
	if (error) {
		[resultDictionary setObject:error forKey:@"error"];
	} else {
		NSString* resultString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
		[resultDictionary setObject:resultString ? resultString : @"" forKey:@"resultString"];
	}
	[[[[[self class] alloc] init] autorelease] performSelectorOnMainThread:@selector(performBlockOnMainThread:)
																withObject:resultDictionary waitUntilDone:YES];
	[pool release];
}
- (void)performBlockOnMainThread:(NSDictionary*)params
{
	NSError* error = [params objectForKey:@"error"];
	NSString* resultString = [params objectForKey:@"resultString"];
	void(^successBlock)(NSString *) = [params objectForKey:@"successBlock"];
    void(^failureBlock)(NSError *) = [params objectForKey:@"failureBlock"];
	if(error)
    {
        failureBlock(error);
    }
    else
    {
        successBlock(resultString);
    }
    Block_release(successBlock);
    Block_release(failureBlock);
}
@end

使い方

- (IBAction)asyncRequestTest
{
	[MyHTTPDownloader asyncRequest:@"http://www.yahoo.com/"
						   success:^(NSString* result) {
							   NSLog(@"yahoo: %@", [result substringToIndex:100]);
							   // ここで、この結果をうけて次の処理を書いたりだとか。
						   }
						   failure:^(NSError* error) {
						   }];
	[MyHTTPDownloader asyncRequest:@"http://twitter.com"
						   success:^(NSString* result) {
							   // 非同期処理でブロック(ややこしい)しないので、こっちが先に呼ばれることもあります。
							   NSLog(@"twitter: %@", [result substringToIndex:100]);
						   }
						   failure:^(NSError* error) {
						   }];
}

結論

やっぱり便利そう。どんどん使っていきたい。

Pocket

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です