前回はRSSフィードの一覧を読み込み、ダウンロードを行うところまで解説した。今回は、複数のフィードを並行してダウンロードするためのテクニックについて解説しよう。

NSOperationを使う

前回まででとりあえず、フィードをダウンロードできる。だが、このままではいささか使いづらい。RSSリーダでは、複数のフィードを一度にダウンロードすることが求められる。しかし、数十個のNSURLConnectionを一気に作成したら、帯域を食いつぶしてしまうだろう。同時に行うダウンロード数は、制限できるのが望ましい。1つのダウンロードが終わってから、次のダウンロードを行うようにするのだ。

Cocoaには、このような制御を行う仕組みがある。NSOperationとNSOperationQueueだ。NSOperationは、同時並行に行われる、それぞれの処理を表す。複数のNSOperationの処理を制御するのが、NSOperationQueueだ。いくつかの処理を並行して行い、1つの処理が終わったら、待機中の次の処理を行うようにする。これを使って、フィードのダウンロードを行ってみよう。

まず、ダウンロードを行うための、NSOperationのサブクラスを作る。これを、DownloadOperationという名前にしよう。DownloadOperationは、初期化時にNSURLRequestを指定するようにする。そして、ダウンロードしたデータを取り出すための、プロパティを持つ。

List 4.

@interface DownloadOperation : NSOperation
{
    NSURLRequest*       _request;
    NSURLConnection*    _connection;
    NSMutableData*      _data;
    BOOL                _isExecuting, _isFinished;
}

// プロパティ
@property (readonly) NSData* data;

// 初期化
- (id)initWithRequest:(NSURLRequest*)request;

@end

_isExecutingや_isFinishedは、処理実行のフラグになる。これらの使い方については、後述する。

このクラスでは、いくつかのNSOperationのメソッドを上書きするのだが、重要なのはstartメソッドだ。このメソッドの中で、処理を開始する。行うのはもちろん、ダウンロードだ。NSURLConnectionを作成して、ダウンロードを開始する。

List 5.

- (void)start
{
    // ダウンロードを開始する
    if (![self isCancelled]) {
        // フラグを設定する
        [self setValue:[NSNumber numberWithBool:YES] forKey:@"isExecuting"];

        // コネクションを作成する
        [NSURLConnection connectionWithRequest:_request delegate:self];
    }
}

あとは、NSURLConnectionのデリゲートメソッドの中で、適切な処理を行ってやればいい。

これを使って、フィードをダウンロードしてみよう。まず、NSOperationQueueクラスのインスタンスを作成する。そして、DownloadOperationのインスタンスを作成し、次々とNSOperationQueueに追加してやればいい。そうすれば、適切に同時並行で処理されていくはずだ。

List 6.

    // NSOperationQueueを作成する
    _queue = [[NSOperationQueue alloc] init];

    // フィードをダウンロードする
    for (NSString* feed in feeds) {
        // URLを指定する
        NSURLRequest*   request;
        request = [NSURLRequest requestWithURL:[NSURL URLWithString:feed]];

        // DownloadOperationを作成する
        DownloadOperation*  operation;
        operation = [[DownloadOperation alloc] initWithRequest:request];
        [operation autorelease];

        // キー値監視を登録する
        [operation addObserver:self forKeyPath:@"isFinished"
                options:NSKeyValueObservingOptionNew context:nil];

        // DownloadOperationを追加する
        [_queue addOperation:operation];
    }

キー値監視で終了通知を受け取る

これで、フィードのダウンロードが開始される。では、ダウンロードが終了した事は、どうやって受け取ればいいのだろうか? これには、キー値コーディングおよびキー値監視が使える。

キー値コーディングとキー値監視は、もともとはCocoaバインディングを実現するために導入された仕組みだ。iPhoneではCocoaバインディングは使えないが、これらの仕組みは使えるようになっている。これにより、NSNotificationとはまた別の種類の通知を行えるようになっている。

DownloadOperationをキー値コーディングに対応させるために、インスタンス変数として_isExecutingと_isFinishedを用意している。それぞれ、ダウンロード処理が実行中かどうか、完了したかどうかを表すものである。

List 5.でダウンロードを行うときに、setValue:forKey:メソッドを使ってフラグを設定した。あれが、キー値コーディングによる設定である。同様に、ダウンロードが終了したときも、isExecutingおよびisFinishedのフラグを設定することになる。このメソッドを使った値の設定を行うと、キー値監視を行っているオブジェクトに通知が飛ぶ。

通知を受け取る側は、キー値監視を行う。これには、List 6.で行っているように、addObserver:forKeyPath:options:context:を使う。通知を受け取ると、observeValueForKeyPath:ofObject:change:context:が呼び出される。確認のために、このメソッドでダウンドードしたデータの大きさを表示させてみよう。

List 7.

- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object
        change:(NSDictionary*)change context:(void*)context
{
    // データの長さを取得する
    unsigned int    length;
    length = [((DownloadOperation*)object).data length];
    NSLog(@"data length %d", length);

    // キー値監視を解除する
    [object removeObserver:self forKeyPath:keyPath];
}

これで、複数のフィードをスマートにダウンロードできる。次回は、ダウンロードしたデータの処理について説明しよう。

ここまでのソースコード: RSS-1.zip