Created
June 17, 2010 02:49
-
-
Save akisute/441620 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
@interface FetchOperation : NSOperation { | |
NSURLRequest *request; | |
NSURLConnection *connection; | |
NSHTTPURLResponse *response; | |
NSMutableData *responseBody; | |
NSOperation *parseOperation; | |
BOOL ready; | |
BOOL executing; | |
BOOL finished; | |
BOOL cancelled; | |
} | |
@property (nonatomic, copy) NSURLRequest *request; | |
- (void)parseResponseBody:(NSData *)bodyData; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "FetchOperation.h" | |
#import "AppDelegate.h" | |
@implementation FetchOperation | |
#pragma mark Properties | |
@synthesize request; | |
- (void)setReady:(BOOL)b { | |
if (ready != b) { | |
[self willChangeValueForKey:@"isReady"]; | |
ready = b; | |
[self didChangeValueForKey:@"isReady"]; | |
} | |
} | |
- (void)setExecuting:(BOOL)b { | |
if (executing != b) { | |
[self willChangeValueForKey:@"isExecuting"]; | |
executing = b; | |
[self didChangeValueForKey:@"isExecuting"]; | |
} | |
} | |
- (void)setFinished:(BOOL)b { | |
if (finished != b) { | |
[self willChangeValueForKey:@"isFinished"]; | |
finished = b; | |
[self didChangeValueForKey:@"isFinished"]; | |
} | |
} | |
- (void)setCancelled:(BOOL)b { | |
if (cancelled != b) { | |
[self willChangeValueForKey:@"isCancelled"]; | |
cancelled = b; | |
[self didChangeValueForKey:@"isCancelled"]; | |
} | |
} | |
#pragma mark Init/dealloc | |
- (id) init { | |
if (self = [super init]) { | |
parseOperation = nil; | |
ready = YES; | |
executing = NO; | |
finished = NO; | |
cancelled = NO; | |
} | |
return self; | |
} | |
- (void) dealloc { | |
[request release]; | |
[connection release]; | |
[response release]; | |
[responseBody release]; | |
if (parseOperation) { | |
[parseOperation removeObserver:self forKeyPath:@"isFinished"]; | |
[parseOperation removeObserver:self forKeyPath:@"isCancelled"]; | |
[parseOperation release]; | |
parseOperation = nil; | |
} | |
[super dealloc]; | |
} | |
#pragma mark NSOperation | |
- (BOOL)isConcurrent { | |
// FetchOperation runs in concurrent mode, managed manually. | |
return YES; | |
} | |
- (void)start { | |
// Follows the behavior of NSOperation in Mac OS 10.6 (and iPhone OS 3.0) | |
// また、start前にrequestがセットされていない場合にも例外を発生させる | |
if (finished || cancelled) { | |
[self cancel]; | |
return; | |
} | |
if (!ready || executing || !request) { | |
@throw NSInvalidArgumentException; | |
} | |
// サーバーと通信可能かどうかを確認する | |
// 通信出来ない場合には何もしないで終了する | |
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate; | |
if (!appDelegate.internetConnectionAvailable) { | |
NSLog(@"Device is not reachable to the API server. Aborting."); | |
[self cancel]; | |
return; | |
} | |
// connectionの準備を行う。準備だけ。実行はmainで非同期的に。 | |
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; | |
if (!connection) { | |
@throw NSInvalidArgumentException; | |
} | |
// main実行直前に状態を変更する、その後実行。 | |
[self setReady:NO]; | |
[self setExecuting:YES]; | |
[self setFinished:NO]; | |
[self setCancelled:NO]; | |
// mainのためにスレッドを作る必要がない、main自体が非同期関数だけで出来ているので | |
[self main]; | |
} | |
- (void)cancel { | |
// 通信をキャンセルして、状態をキャンセル状態にする。 | |
[connection cancel]; | |
[self setReady:NO]; | |
[self setExecuting:NO]; | |
[self setFinished:YES]; | |
[self setCancelled:YES]; | |
} | |
- (BOOL)isReady { | |
return ready; | |
} | |
- (BOOL)isExecuting { | |
return executing; | |
} | |
- (BOOL)isFinished { | |
return finished; | |
} | |
- (BOOL)isCancelled { | |
return cancelled; | |
} | |
- (void)main { | |
// DO NOT override this method in subclasses. Use parseResponseBody instead. | |
// iOS4に対応するため、currentRunLoopではなくmainRunLoopを使う必要があります。 | |
// ※iOS4はMac OS 10.6と同じNSOperationの挙動になるため。詳細はブログに書きます | |
NSLog(@" * Executing API %@", [self.request URL]); | |
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; | |
[connection start]; | |
} | |
#pragma mark NSURLConnection | |
- (void)connection:(NSURLConnection*)sender didReceiveResponse:(NSHTTPURLResponse*)res { | |
NSLog(@" * Response received for API %@", [self.request URL]); | |
// didReceiveResponseは極稀にひとつの接続で複数回呼ばれることがあるので、その点に注意して書く。 | |
// まずはレスポンスとボディを格納する準備をする。 | |
if (response) { | |
[response release]; | |
response = nil; | |
} | |
if (responseBody) { | |
[responseBody release]; | |
responseBody = nil; | |
} | |
responseBody = [[NSMutableData alloc] init]; | |
// レスポンスが飛んできたので確保する | |
// レスポンスが200以外であれば即座にキャンセルして終了する | |
response = [res retain]; | |
if ([response statusCode] != 200) { | |
NSLog(@"Error while executing API: invalid HTTP status code %d has been returned", [response statusCode]); | |
[self cancel]; | |
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate; | |
[appDelegate showConnectionAlertOnMainThreadWithMessage:@"エラーだよーん"]; | |
return; | |
} | |
} | |
- (void)connection:(NSURLConnection*)sender didReceiveData:(NSData*)data { | |
// データが送られてきたら都度キャッシュに突っ込む | |
[responseBody appendData:data]; | |
} | |
- (void)connection:(NSURLConnection*)sender didFailWithError:(NSError*)error { | |
// 接続に失敗したらキャンセル状態にして、タイムアウトエラーメッセージを出す | |
[self cancel]; | |
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate; | |
[appDelegate showConnectionAlertOnMainThreadWithMessage:@"接続失敗だよーん"]; | |
} | |
- (void)connectionDidFinishLoading:(NSURLConnection*)sender { | |
NSLog(@" * Data load completed for API %@", [self.request URL]); | |
// parseMainを別スレッドで起動して続きを行う | |
// スレッドの完了通知を受け取ってからこのオペレーションを終了させること | |
parseOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(parseMain:) object:nil]; | |
[parseOperation addObserver:self | |
forKeyPath:@"isFinished" | |
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) | |
context:NULL]; | |
[parseOperation addObserver:self | |
forKeyPath:@"isCancelled" | |
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) | |
context:NULL]; | |
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate; | |
[appDelegate.parseOperationQueue addOperation:parseOperation]; | |
} | |
- (NSURLRequest *)connection:(NSURLConnection *)sender willSendRequest:(NSURLRequest *)req redirectResponse:(NSURLResponse *)res { | |
// リダイレクト要請は常に受け入れてリダイレクトする | |
return req; | |
} | |
#pragma mark Method | |
- (void)parseResponseBody:(NSData *)bodyData { | |
// Default implementation does nothing. | |
// Override this method to parse the response data. | |
return; | |
} | |
#pragma mark NSThread | |
- (void)parseMain:(id)sender { | |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | |
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate; | |
// すべての通信が完了したので、responseBodyのチェック。responseBodyがnullであれば何らかのエラーがあったと考えられるのでキャンセルして終了。 | |
if (!response || !responseBody) { | |
NSLog(@"Error while executing API: Neither response nor body are returned from the server."); | |
[self cancel]; | |
[appDelegate showConnectionAlertOnMainThreadWithMessage:@"レスポンスが帰ってこなかったよーん"]; | |
return; | |
} | |
// チェック完了。responseBodyを渡してparseする。 | |
// parseResponseBodyは同期的に、かつロックを掛けて実行されるので注意。ロックの解放はfinallyで行う。 | |
@try { | |
[appDelegate.managedObjectContext lock]; | |
[self parseResponseBody:responseBody]; | |
} | |
@catch (NSException *e) { | |
// DO NOT throw exceptions out from NSOperation in order not to stop the entire application. | |
NSLog(@"Error while parsing API response: %@", e); | |
} | |
@finally { | |
[appDelegate.managedObjectContext unlock]; | |
// パースも完了、すべての処理が終了。pool releaseはfinallyに入れておかないとエラー時に実行されなくなりメモリが漏れる | |
[pool release]; | |
} | |
} | |
#pragma mark - | |
#pragma mark NSKeyValueObserving | |
- (void)observeValueForKeyPath:(NSString *)keyPath | |
ofObject:(id)object | |
change:(NSDictionary *)change | |
context:(void *)context { | |
// ParseOperationの状況に応じて自身の状況を変化させる | |
if ([keyPath isEqual:@"isFinished"]) { | |
[self setReady:NO]; | |
[self setExecuting:NO]; | |
[self setFinished:YES]; | |
[self setCancelled:NO]; | |
} else if ([keyPath isEqual:@"isCancelled"]) { | |
[self cancel]; | |
} | |
// ParseOperationを開放する | |
[parseOperation removeObserver:self forKeyPath:@"isFinished"]; | |
[parseOperation removeObserver:self forKeyPath:@"isCancelled"]; | |
[parseOperation release]; | |
parseOperation = nil; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment