iPhone SDKレシピ2:NSURLConnectionを使ってファイルをダウンロードする

CocoaフレームワークにはNSURLDownloadという便利なクラスが用意されているが、iPhone SDK (UIKit) には入っていない。そこで、NSURLConnectionを使ってファイルをダウンロードするための簡単なラッパークラスURLDownloadを用意し、それを用いてファイルをダウンロードする。
Basic認証が必要なURLからのダウンロードにはまだ対応していないが、 didReceiveAuthenticationChallenge のところでUIAlertViewでも出して、いろいろやってやればできるはず。

使用例

このクラスを使うには、 URLDownloadDelegagte プロトコルを実装したクラスを delegate として URLDownload に渡してやればよい。

URLDownload *downloader;
//  ダウンロード開始
- (IBAction) download:(id)sender {
	[urlField resignFirstResponder];
	NSString *tmpPath = [NSHomeDirectory() stringByAppendingPathComponent:@"tmp"];
	NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:[urlField text]]];

	// ここでダウンロード開始
	downloader = [[URLDownload alloc] initWithRequest:req directory:tmpPath delegate:self];
}

///////////////////////////////////////////////////////////////////////
//URLDownloadDelegate implements
// ダウンロード完了
- (void)downloadDidFinish:(URLDownload *)download {
	LOG(download.filePath);
	[self releaseDownloader];
}

// ダウンロードをキャンセルした際に呼ばれる
- (void)download:(URLDownload *)download didCancelBecauseOf:(NSException *)exception {
	UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"" message:[exception reason] delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK",nil] autorelease];
	[alert show];
	[self releaseDownloader];
}

// ダウンロードに失敗した際に呼ばれる
- (void)download:(URLDownload *)download didFailWithError:(NSError *)error {
	UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"" message:[error localizedDescription] delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK",nil] autorelease];
	[alert show];
	[self releaseDownloader];
}

// ダウンロード進行中に呼ばれる
- (void)download:(URLDownload *)download didReceiveDataOfLength:(NSUInteger)length {
	// プログレスバーの表示でもやる
}

URLDownload.h

#import <Foundation/Foundation.h>

@class URLDownload;

@protocol URLDownloadDeleagte
- (void)downloadDidFinish:(URLDownload *)download;
- (void)download:(URLDownload *)download didCancelBecauseOf:(NSException *)exception;
- (void)download:(URLDownload *)download didFailWithError:(NSError *)error;
@optional
- (void)download:(URLDownload *)download didReceiveDataOfLength:(NSUInteger)length;
- (void)download:(URLDownload *)download didReceiveResponse:(NSURLResponse *)response;

@end

@interface URLDownload : NSObject {
	id <URLDownloadDeleagte, NSObject> delegate;
	NSString *directoryPath;
	NSString *filePath;
	NSURLRequest *request;
	NSFileHandle *file;
	NSURLConnection *con;
}
@property(readonly) NSString *filePath;

- (id)initWithRequest:(NSURLRequest *)req directory:(NSString *)dir delegate:(id<URLDownloadDeleagte, NSObject>)dg;
- (void)dealloc;
- (void)cancel;

// NSURLConnection delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
//- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
//- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
//- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse;
//- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;

@end

URLDownload.m

#import "URLDownload.h"


@implementation URLDownload
@synthesize filePath;

- (id)initWithRequest:(NSURLRequest *)req directory:(NSString *)dir delegate:(id<URLDownloadDeleagte, NSObject>)dg {
	if (self = [super init]) {
		request = [req retain];
		directoryPath = [dir retain];
		delegate = [dg retain];
		
		con = [[NSURLConnection alloc] initWithRequest:request delegate:self];
	}
	return self;
}

- (void)dealloc {
	[request release];
	[directoryPath release];
	[filePath release];
	[file release];
	[delegate release];
	[con release];
	[super dealloc];
}

- (void)cancel {
	[con cancel];
}

// NSURLConnection delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
	filePath = [[response suggestedFilename] retain];
	if ([delegate respondsToSelector:@selector(download: didReceiveResponse:)])
		[delegate download:self didReceiveResponse:response];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
	LOG([error localizedDescription]);
	if ([delegate respondsToSelector:@selector(download: didFailWithError:)])
		[delegate download:self didFailWithError:error];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
	@try {
		if (file == nil) {
			NSFileManager *fm = [NSFileManager defaultManager];
			BOOL isDir;
			if (![fm fileExistsAtPath:directoryPath isDirectory:&isDir]) {
				NSError *error;
				if (![fm createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error]) {
					LOG([error localizedDescription]);
					LOG([error localizedFailureReason]);
					LOG([error localizedRecoverySuggestion]);
					[NSException raise:@"Exception" format:[error localizedDescription]];
				}
			} else if (!isDir) {
				[NSException raise:@"Exception" format:@"Failed to create directory at %@, because there is a file already.", directoryPath];
			}
			NSString *tmpFilePath = [[directoryPath stringByAppendingPathComponent:filePath] stringByStandardizingPath];
			int suffix = 0;
			while ([fm fileExistsAtPath:tmpFilePath]) {
				suffix++;
				tmpFilePath = [[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@%d", filePath, suffix]] stringByStandardizingPath];
			}
			[fm createFileAtPath:tmpFilePath contents:[NSData data] attributes:nil];
			[filePath release];
			filePath = [tmpFilePath retain];
			
			file = [[NSFileHandle fileHandleForWritingAtPath:filePath] retain];
		}
		[file writeData:data];
		if ([delegate respondsToSelector:@selector(download: didReceiveDataOfLength:)])
			[delegate download:self didReceiveDataOfLength:[data length]];
	}
	@catch (NSException * e) {
		LOG([e reason]);
		[connection cancel];
		[delegate download:self didCancelBecauseOf:e];
	}
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
	[delegate downloadDidFinish:self];
}
/*

 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
 - (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
 - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;
 - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse;
*/
 
@end