AFNetworking嵌套调用

时间:2013-08-14 03:21:57

标签: iphone ios afnetworking

现在,我得到了一个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) {
    }];
}

3 个答案:

答案 0 :(得分:4)

如果我理解你的问题,我认为你所看到的是异步问题。

您正在尝试遍历您的照片字典,通过发送另一个GET请求来获取每张照片的照片大小,这是一种异步操作。但是,正因为如此,循环的下一次迭代已经在先前的异步操作完成之前执行。

在这种情况下,你可以做的是使用递归来帮助你“迭代”或“循环”你的照片词典。

要求

要使以下代码生效,您需要创建2个属性

  • 在yourClass.h文件中存储“照片 NSDictionary (例如NSDictionary * photosDict)的属性
  • 存储“照片”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];
        }];
    }
}

希望有所帮助。

答案 1 :(得分:2)

除了@Zhang的优秀答案之外,我想描述一下OP面临的常见问题以及一般的解决方案"这个常见问题可能看起来像。

共同目标是:

  1. 从服务器获取项目列表。每个项目都包含一个指向其他资源的URL(例如图像)。

  2. 收到列表后,对于列表中的每个项目,获取URL提供的资源(图像)。

  3. 以同步方式实现此解决方案时,解决方案显而易见,实际上非常简单。但是,当采用异步方式 - 这是进行网络连接时的首选方式 - 可行的解决方案变得异常复杂,除非您知道如何解决此类问题;)

    这里有趣的部分是#2。部分#1可以通过异步调用和完成函数简单地完成,其中完成函数调用部分#2。

    为了使事情更容易理解,我将做一些简化和一些先决条件:

    1. 在第1部分中,我们获得了一个元素列表,比如包含我们元素的NSArray对象。每个元素都有一个属性,该属性是另一个资源的URL。

      现在,我们可以很容易地假设我们已经拥有表示N个输入值的元素数组,这些元素将在循环中异步处理 - 一个接一个。让我们将该数组命名为#34; Source Array"。

    2. 我们将处理异步方法/函数。使方法/函数信号完成异步处理的一种方法是完成处理程序(块)。

      所有完成处理程序的公共签名将定义如下:

      typedef void (^completion_t)(id result);

      注意: result 应代表异步函数或方法的最终结果。它可能是我们期望的东西(例如图像),或者它可能表示错误,例如通过传递和NSError对象。

    3. 为了实现我们的第2部分,我们需要一个异步方法/函数,它接受一个输入(来自输入数组的一个元素)并产生一个输出。这对应于您的"获取图像资源"任务。稍后我们需要为"输入数组"的每个元素应用此方法/函数。我们得到了第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);
      
    4. 请注意,转换函数或方法的结果将通过完成处理程序的参数传递。 同步函数只有一个返回值并返回结果。

      注意:名称"转换"只是一个通用名称。您可以将网络请求包装在一个方法中,并获得这样的"转换"功能。在OP的示例中, URL 将是输入参数,而完成处理程序的结果参数将是从服务器获取的图像(或错误)。

      注意:这和以下简化只是为了使异步模式的解释更容易理解。在实践中,异步函数或方法可以采用其他输入参数,而完成处理程序也可以具有其他参数。


      现在,越多"棘手"部分:

      以异步方式实现循环

      嗯,这有点"不同"比同步编程风格。

      有目的地,我们定义了某种 forEach 函数或方法进行此迭代。该函数或方法本身是异步的!我们现在知道任何异步函数或方法都有一个完成处理程序。

      因此,在函数的情况下,我们可以声明我们的" forEach"功能如下:

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

      transform_each 顺序将异步转换函数 task 应用于输入数组 inArray 中的每个对象。完成处理所有输入后,它将调用完成处理程序完成

      完成处理程序的结果参数是一个数组,其中包含每个转换函数的结果,其顺序与相应的输入相同。

      注意:"顺序"这意味着输入一个接一个地处理。该模式的变体可以并行处理输入。

      参数 inArray 是我们的"输入数组"从步骤#1收集。

      参数 task 是我们的异步转换函数,几乎可以是任何接受输入并产生输出的函数。这将是我们的异步"获取图像"来自OPs示例的任务。

      参数 completion 是处理所有输入时调用的处理程序。它的参数包含数组中每个变换函数的输出。

      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函数)是将从转换函数的完成处理程序调用{​​{1}}。这可能看起来像递归,但实际上并非如此。

      参数 iter 指向要处理的数组中的当前对象。 它还将用于确定停止条件:当枚举数指向结束时,我们从方法do_each获得nil结果。这最终会阻止循环。

      否则,将使用当前对象作为输入参数调用转换函数 task 。该对象将按任务的定义进行异步处理。完成后,将调用任务的完成处理程序。它的参数 result 将是转换函数的输出。处理程序需要将结果添加到结果数组 outArray 。然后它再次调用帮助器nextObject。这似乎是一个递归调用,但事实并非如此:前一个do_each已经被返回。这只是 do_each的另一个调用。

      完成后,我们就可以完成do_each功能,如下所示:

      transform_each

      NSArray类别

      为方便起见,我们可以轻松地为NSArray创建一个类别,其中包含" forEach"顺序异步处理输入的方法:

      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);
      }
      

      可以在Gist上找到代码示例:transform_each

      解决常见异步模式的更为复杂的概念是利用" Futures"或"承诺"。我实施了“承诺”的概念。对于小型图书馆中的Objective-C:RXPromise

      以上"循环"可以实现包括通过RXPromise 取消异步任务的能力,当然还有更多。玩得开心;)

答案 2 :(得分:0)

我想我可能只是解决了这个问题。我不确定。它只是有效。我正在使用AFNetwroking的enqueueBatchOfHTTPRequestOperations函数。