现在,我得到了一个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) {
}];
}
答案 0 :(得分:4)
如果我理解你的问题,我认为你所看到的是异步问题。
您正在尝试遍历您的照片字典,通过发送另一个GET请求来获取每张照片的照片大小,这是一种异步操作。但是,正因为如此,循环的下一次迭代已经在先前的异步操作完成之前执行。
在这种情况下,你可以做的是使用递归来帮助你“迭代”或“循环”你的照片词典。
要使以下代码生效,您需要创建2个属性
在原始方法中,存储照片字典,然后存储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面临的常见问题以及一般的解决方案"这个常见问题可能看起来像。
共同目标是:
从服务器获取项目列表。每个项目都包含一个指向其他资源的URL(例如图像)。
收到列表后,对于列表中的每个项目,获取URL提供的资源(图像)。
以同步方式实现此解决方案时,解决方案显而易见,实际上非常简单。但是,当采用异步方式 - 这是进行网络连接时的首选方式 - 可行的解决方案变得异常复杂,除非您知道如何解决此类问题;)
这里有趣的部分是#2。部分#1可以通过异步调用和完成函数简单地完成,其中完成函数调用部分#2。
为了使事情更容易理解,我将做一些简化和一些先决条件:
在第1部分中,我们获得了一个元素列表,比如包含我们元素的NSArray
对象。每个元素都有一个属性,该属性是另一个资源的URL。
现在,我们可以很容易地假设我们已经拥有表示N个输入值的元素数组,这些元素将在循环中异步处理 - 一个接一个。让我们将该数组命名为#34; Source Array"。
我们将处理异步方法/函数。使方法/函数信号完成异步处理的一种方法是完成处理程序(块)。
所有完成处理程序的公共签名将定义如下:
typedef void (^completion_t)(id result);
注意: result 应代表异步函数或方法的最终结果。它可能是我们期望的东西(例如图像),或者它可能表示错误,例如通过传递和NSError
对象。
为了实现我们的第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);
请注意,转换函数或方法的结果将通过完成处理程序的参数传递。 同步函数只有一个返回值并返回结果。
注意:名称"转换"只是一个通用名称。您可以将网络请求包装在一个方法中,并获得这样的"转换"功能。在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函数。