0

今、私は GET リクエストを受け取りました。それが終了した後、json を取得しました。次に、json の ID を使用して別のフェッチ リクエストを実行したいと考えています。これは、次々とネストされたフェッチ リクエストのようなものです。例えば:

+ (void)searchPhotoWithTags:(NSArray *)tags page:(NSInteger)page perPage:(NSInteger)perPage completionBlock:(void (^)(NSArray *, NSError *))block
{
    NSDictionary *dict = @{@"method": @"search", @"api_key": kAppKey, @"tags": [tags componentsJoinedByString:@","], @"per_page": [NSString stringWithFormat:@"%d", perPage], @"page": [NSString stringWithFormat:@"%d", page], @"format": @"json"};
    [[LDHttpClient sharedClient] getPath:@"" parameters:dict success:^(AFHTTPRequestOperation *operation, id responseObject) {      
        [[responseObject valueForKeyPath:@"photos.photo"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            Photo *photo = [[Photo alloc] initWithPhotoId:[NSNumber numberWithInt:[obj[@"id"] integerValue]] andSecret:obj[@"secret"]];
            //down below, I want to use photo.photoId to execute another request but the data is not completed. what's the better way to do this?
            [PhotoSize getPhotoSizesWithPhotoId:photo.photoId completionBlock:^(NSArray *photoSizes, NSError *error) {
                [photos addObject:@{@"photo": photo, @"sizes": photoSizes}];
            }];
        }];
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    }];
}
4

3 に答える 3

4

あなたの質問を正しく理解していれば、あなたが目撃しているのは非同期の問題だと思います。

写真ディクショナリをループして、非同期操作である別の GET 要求を送信して各写真の写真サイズを取得しようとしています。ただし、そのため、前の非同期操作が完了する前に、ループの次の反復が既に実行されています。

この場合、再帰を使用して、写真辞書を「反復」または「ループ」することができます。

要件

以下のコードが機能するには、2 つのプロパティを作成する必要があります。

  • " photos " NSDictionary (例: NSDictionary *photosDict) を yourClass.h ファイルに格納するためのプロパティ
  • 「photos」NSDictionary の列挙子を格納するための別のプロパティ。これはNSEnumerator型で、「 photosEnum」と呼ぶことができます。

コードを少しクリーンアップする

元のメソッドでは、写真辞書を保存し、そこから photosEnum 列挙子も保存します。

+ (void)searchPhotoWithTags:(NSArray *)tags page:(NSInteger)page perPage:(NSInteger)perPage completionBlock:(void (^)(NSArray *, NSError *))block
{
    NSDictionary *dict = @{@"method": @"search", @"api_key": kAppKey, @"tags": [tags componentsJoinedByString:@","], @"per_page": [NSString stringWithFormat:@"%d", perPage], @"page": [NSString stringWithFormat:@"%d", page], @"format": @"json"};

    [[LDHttpClient sharedClient] getPath:@"" parameters:dict success:^(AFHTTPRequestOperation *operation, id responseObject) {      

        // I assume you have a property of type NSDictionary created called "photos"
        self.photosDict = [responseObject valueForKeyPath:@"photos"];

        // Also create a property for the enumerator of type NSEnumerator
        self.photosEnum = [self.photosDict objectEnumerator];

        // ----------------------------------------------------------
        // First call of our recursion method
        //
        // This will start our "looping" of our photos enumerator
        // -----------------------------------------------------------
        [self processPhotoDictionary];

    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"Failed to get photos, error: %@", [error localizedDescription]);
    }];
}

最後に、再帰メソッドが photoSizes の処理を​​行います。

-(void)processPhotoDictionary
{
    // ------------------------------------------------------
    // Because self.photosEnum is a property of our class
    // it remembers where it is "up to" in the "looping"
    // ------------------------------------------------------
    NSDictionary *photo = [self.photosEnum nextObject];

    if(photo != nil)
    {
        Photo *photoObj = [[Photo alloc] initWithPhotoId:[NSNumber numberWithInt:[[photo valueForKey:@"id"] integerValue]] 
                                            andSecret:[photo valueForKey:@"secret"]];

        [PhotoSize getPhotoSizesWithPhotoId:photoObj.photoId completionBlock:^(NSArray *photoSizes, NSError *error) {
            [photos addObject:@{@"photo": photoObj, @"sizes": photoSizes}];

            // ------------------------------------------------------
            // Here we're using recursion to iterate through our
            // enumerator due to asynchronous nature instead of the
            // while loop.
            // ------------------------------------------------------
            [self processPhotoDictionary];
        }];
    }
}

それが役立つことを願っています。

于 2013-08-14T07:02:25.297 に答える
2

@Zhangの優れた回答に加えて、OPが直面している一般的な問題と、この一般的な問題に対する「一般的な解決策」がどのように見えるかについて説明したいと思います。

共通の目的は次のとおりです。

  1. サーバーからアイテムのリストを取得します。各アイテムには、他のリソース (画像など) を指す URL が含まれています。

  2. リストを受け取ったら、リスト内の各項目について、URL で指定されたリソース (画像) をフェッチします。

これを同期スタイルで実装すると、解決策は明らかで、実際には非常に簡単です。しかし、ネットワーキングを行う際に好まれる方法である非同期スタイルを採用する場合、そのような問題を解決する方法を知らない限り、実行可能なソリューションは驚くほど複雑になります;)

ここで興味深いのは #2 です。パート 1 は、非同期呼び出しと、完了関数がパート 2 を呼び出す完了関数を介して簡単に実行できます。

物事をより理解しやすくするために、いくつかの単純化といくつかの前提条件を作成します。

  1. パート 1 では、要素のリスト、たとえば要素NSArrayを含むオブジェクトを取得します。各要素には、別のリソースへの URL であるプロパティがあります。

    ここで、ループ内で次々と非同期に処理される N 個の入力値を表す要素の配列が既にあると簡単に仮定できます。その配列に「ソース配列」という名前を付けましょう。

  2. 非同期メソッド/関数を扱います。何かの処理が非同期で終了したことをメソッド/関数に通知させる 1 つの方法は、完了ハンドラー (ブロック) です。

    すべての完了ハンドラの共通シグネチャは次のように定義されます。

    typedef void (^completion_t)(id result);

    注: resultは、非同期関数またはメソッドの最終的な結果を表すものとします。NSErrorこれは、私たちが期待する種類のもの (画像など) である場合もあれば、パスやオブジェクトなどのエラーを示している場合もあります。

  3. パート 2 を実装するには、入力 (入力配列の 1 つの要素) を受け取り、出力を生成する非同期メソッド/関数が必要です。これは、「画像リソースの取得」タスクに対応します。後で、パート 1 で取得した「入力配列」の各要素にこのメソッド/関数を適用する必要があります。

    一般的な関数である「変換関数」には、次のシグネチャがあります。

    void transform(id input, completion_t completion);

    対応するメソッドには、次の署名があります。

    -(void) transformWithInput:(id)input 
                    completion:(completion_t)completionHandler;
    

    関数の typedef を次のように定義できます。

    typedef void (^transform_t)(id input, completion_t completion);
    

変換関数またはメソッドの結果は、完了ハンドラのパラメータを介して渡されることに注意してください。同期関数は戻り値を持ち、結果を返します。

注: 「変換」という名前は単なる総称です。ネットワーク要求をメソッドにラップして、そのような種類の「変換」機能を取得できます。OP の例では、URLが入力パラメーターになり、完了ハンドラーの結果パラメーターはサーバーからフェッチされた画像 (またはエラー) になります。

注: これと次の単純化は、非同期パターンの説明を理解しやすくするために行ったものです。実際には、非同期関数またはメソッドは他の入力パラメーターを受け取る場合があり、完了ハンドラーも他のパラメーターを持つ場合があります。


さて、より「トリッキーな」部分:

非同期スタイルでループを実装する

これは、同期プログラミング スタイルとは少し「異なります」。

意図的に、この反復を行う何らかのforEach関数またはメソッドを定義します。その関数またはメソッド自体が非同期です。そして、非同期関数またはメソッドには完了ハンドラーがあることがわかりました。

したがって、関数の場合、「forEach」関数を次のように宣言できます。

`void transform_each(NSArray* inArray, transform_t task, completion_t completion);`

transform_each 入力配列inArray内の各オブジェクトに非同期変換関数タスクを順次適用します。すべての入力の処理が完了すると、完了ハンドラーのcompletionが呼び出されます。

完了ハンドラの結果パラメータは、対応する入力と同じ順序で各変換関数の結果を含む配列です。

注: ここでの「順次」とは、入力が次々に処理されることを意味します。そのパターンの変形は、入力を並行して処理する場合があります。

パラメータinArrayは、ステップ 1 で収集した「入力配列」です。

パラメータタスクは、非同期変換関数であり、事実上、入力を受け取って出力を生成するものであれば何でもかまいません。これは、OP の例からの非同期の「イメージのフェッチ」タスクになります。

パラメータ補完は、すべての入力が処理されたときに呼び出されるハンドラです。そのパラメーターには、配列内の各変換関数の出力が含まれています。

は次のtransform_eachように実装できます。まず、「ヘルパー」関数が必要ですdo_each

do_each実際には、非同期スタイルでループを実装するためのパターン全体の中心であるため、ここで詳しく見てみましょう。

void do_each(NSEnumerator* iter, transform_t task, NSMutableArray* outArray, completion_t completion)
{
    id obj = [iter nextObject];
    if (obj == nil) {
        if (completion)
            completion([outArray copy]);
        return;
    }
    task(obj, ^(id result){
        [outArray addObject:result];
        do_each(iter, task, outArray, completion);
    });
}

ここで興味深い部分と、(for_each 関数として) ループを実装するための "一般的な非同期パターン" または "イディオム"は、変換関数do_eachの完了ハンドラーから呼び出されることです。これは再帰のように見えるかもしれませんが、実際にはそうではありません。

パラメータiterは、処理される配列内の現在のオブジェクトを指します。また、停止条件を決定するためにも使用されます。列挙子が末尾を超えると、nilmethod から結果が得られnextObjectます。これにより、最終的にループが停止します。

それ以外の場合、現在のオブジェクトを入力パラメーターとして変換関数タスクが呼び出されます。オブジェクトは、タスクの定義に従って非同期に処理されます。完了すると、タスクの完了ハンドラが呼び出されます。そのパラメーターの結果は、変換関数の出力になります。ハンドラーは、結果の配列outArrayに結果を追加する必要があります 。次に、ヘルパーdo_eachを再度呼び出します。これは再帰呼び出しのように見えますが、実際にはそうではありません: 前者do_eachは既に返されています。これは の別の呼び出しですdo_each

それができたら、transform_each以下に示すように関数を簡単に完成させることができます。

void transform_each(NSArray* inArray, transform_t task, completion_t completion) {
    NSMutableArray* outArray = [[NSMutableArray alloc] initWithCapacity:[inArray count]];
    NSEnumerator* iter = [inArray objectEnumerator];
    do_each(iter, task, outArray, completion);
}

NSArray カテゴリ

便宜上、入力を順番に非同期に処理する「forEach」メソッドを使用して、NSArray のカテゴリを簡単に作成できます。

@interface NSArray (AsyncExtension)
- (void) async_forEachApplyTask:(transform_t) task completion:(completion_t) completion;
@end

@implementation NSArray (AsyncExtension)
- (void) async_forEachApplyTask:(transform_t) task completion:(completion_t) completion {
    transform_each(self, task, completion);
}
@end

コード例は Gist にあります: transform_each

一般的な非同期パターンを解決するためのより洗練された概念は、「Futures」または「Promises」を利用することです。Objective-C の "Promise" の概念を小さなライブラリRXPromiseに実装しました。

上記の「ループ」は、RXPromise を介して非同期タスクをキャンセルする機能を含めて実装できますが、もちろんそれ以上の機能もあります。楽しむ ;)

于 2013-08-14T09:01:51.607 に答える