循环中的块是否已经全部执行完毕如何判断?

13

我设置了一个循环,用于下载一系列图片,然后使用UIImageViewanimationImages属性进行动画处理。我想知道何时循环内的所有块都执行完毕,以便开始进行动画处理,想知道如何确定它们何时完成执行?谢谢!

for (PFObject *pictureObject in objects){

    PFFile *imageFile = [pictureObject objectForKey:@"image"];
    NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
    NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];

    [tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
       [self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages

    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
        NSLog(@"Error %@", error);
    }];

}
//When I know all the blocks have finished downloading, I will then to animate the downloaded images. 

编辑:遇到 Error -999 问题

当我执行提供的答案中的代码时,遇到了以下问题:Domain=NSURLErrorDomain Code=-999 "The operation couldn’t be completed. (NSURLErrorDomain error -999.)"

快速搜索显示Error -999 的意思是“在上一个请求完成之前发出了另一个请求”,这在这里肯定是正确的,因为我正在快速连续地进行多个请求。推荐的修复方法在这里对我没有用,因为它只会成功下载一个UIImage(最后一个请求),以前的请求都会失败。我想知道这里或AFNetworking中是否有解决方法?谢谢!

编辑2:基于 @David 的解决方案的工作代码

for (PFObject *pictureObject in objects){

    PFFile *imageFile = [pictureObject objectForKey:@"image"];
    NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
    NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
    AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:imageRequest];
    requestOperation.responseSerializer = [AFImageResponseSerializer serializer];

    dispatch_group_enter(group);
    [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"Response: %@", responseObject);
        UIImage *retrivedImage = (UIImage *)responseObject;
        [self.downloadedUIImages addObject:retrivedImage];

        dispatch_group_leave(group);

    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"Image error: %@", error);

        dispatch_group_leave(group);
    }];

    [requestOperation start];
    counter ++;
}

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"Horray everything has completed");
    NSLog(@"What is here %@", self.downloadedUIImages);
    NSLog(@"Done");

});

在我看来,如何在所有任务完成时得到通知并不是你真正面临的问题。更具挑战性的问题是,如果必要的话(在任何实际应用程序中肯定会需要),你的并行任务应该是可取消的。 - CouchDeveloper
感谢@CouchDeveloper的评论。如果这是一个真实的应用程序,您能否澄清一下“任务应该可取消”是什么意思?我对此还比较新,所以如果您认为有关此主题的文献我应该查看,也请在此引用。谢谢! - daspianist
那么,为什么异步任务应该是可取消的呢?假设您的网络停滞不前,并且下载图像可能需要很长时间。现在,您是否要强制用户等待,无论如何,直到所有请求超时 - 还是认为提供“取消”按钮可能对用户友好并且是一个好主意,这样用户可以随时停止这些任务,当她想要的时候?;)提示:NSURLConnection和NSURLSessionTask都可以被取消。注意:接受的答案不适合支持此操作 :/ - CouchDeveloper
1
非常好的问题,对我的项目帮助很大,非常感谢。 - Nishad Arora
4个回答

34

创建一个调度组,在for循环中进入该组,在完成块中离开该组。然后可以使用dispatch_group_notify来查找何时所有块都已完成:

dispatch_group_t    group = dispatch_group_create();

for (PFObject *pictureObject in objects){

    PFFile *imageFile = [pictureObject objectForKey:@"image"];
    NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
    NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];

    dispatch_group_enter(group);
    [tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
        [self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
        dispatch_group_leave(group);
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
        NSLog(@"Error %@", error);
        dispatch_group_leave(group);
    }];

}

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // do your completion stuff here
});

作为后续,我似乎只成功下载了其中一张图片,其余的都出现了错误 Error Domain=NSURLErrorDomain Code=-999 "The operation couldn’t be completed. (NSURLErrorDomain error -999.)。快速搜索显示这与AFNetworking有关...但即使使用此处提供的推荐解决方案(https://dev59.com/7nNA5IYBdhLWcg3wSrqa),我仍然无法下载其余的图像。 - daspianist
另外一条评论:看起来错误代码“-999”表示“在上一个请求完成之前,又发出了另一个请求。”(https://dev59.com/hmQo5IYBdhLWcg3wbexJ)。您是否知道如何同时发送不同的请求以在不同的线程上执行? - daspianist
2
我猜问题出在setImageWithURLRequest这个方法里面。很可能是因为你重复设置了同一个图像视图的URL,这可能会取消之前的请求。不要使用tokenImageView来处理下载,而是更直接地使用AFNetworking或类似的工具来处理它。你可以同时处理多个请求。-999表示请求被取消,而不是重复请求。 - David Berry
太好了 - 这很有帮助。我会尝试另一种方法来下载UIImage。感谢你们两个。 - daspianist
谢谢 - 这是一个很棒的答案! - Michael Royzen
谢谢!这个完美地解决了我的问题,我浪费了很多时间在计数器和其他东西上。你帖子中的例子帮助我在几分钟内解决了它。 - Curious101

2

统计你已经完成的数量。挑战在于使其线程安全。我建议为此创建一个原子计数器类。

通用解决方案!

+ (void)runBlocksInParallel:(NSArray *)blocks completion:(CompletionBlock)completion {
    AtomicCounter *completionCounter = [[AtomicCounter alloc] initWithValue:blocks.count];
    for (AsyncBlock block in blocks) {
        block(^{
            if ([completionCounter decrementAndGet] == 0) {
                if (completion) completion();
            }
        });
    }
    if (blocks.count == 0) {
        if (completion) completion();
    }
}

NSMutableArray *asyncBlocks = [NSMutableArray array];
for (PFObject *pictureObject in objects){
    [asyncBlocks addObject:^(CompletionBlock completion) {
        PFFile *imageFile = [pictureObject objectForKey:@"image"];
        NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
        NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];

        [tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
           [self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
        } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
            NSLog(@"Error %@", error);
        } completion:completion];
    }];
}
[BlockRunner runBlocksInParallel:[asyncBlocks copy] completion:^{
    //Do your final completion here!
}];

你可以通过使用专用的调度队列(可能是主队列)来访问计数器和属性downloadedUIImages,从而使其线程安全。 - CouchDeveloper
@CouchDeveloper 我的AtomicCounter实现使用NSLock使其线程安全。我发现这比搞调度队列更简单。 - CrimsonChris

0

设置一个属性并将其初始化为循环次数 - objects.count。在块的完成中,将数字减少。当您达到零时,任务完成。


如果不同线程上的最后两个块同时完成会怎样? - CrimsonChris
1
@CrimsonChris: setImageWithURLRequest:... 似乎来自于 AFNetworking,它(如果我理解正确的话)默认在主线程上调用完成块。 - Martin R
1
也许那个匿名的投票者会回来并改变他的决定... - Martin R

-3
for (PFObject *pictureObject in objects){

    PFFile *imageFile = [pictureObject objectForKey:@"image"];
    NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
    NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];

    [tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
       [self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
       if([[objects lastObject] isEqual:pictureObject]) {
           [self animateImages];
       }
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
        NSLog(@"Error %@", error);
        if([[objects lastObject] isEqual:pictureObject]) {
           [self animateImages];
       }
    }];

}

- (void)animateImages {
    //do animation here.
}

这样做不行,如果对象以不同的速率下载,它们并不总是按正确的顺序返回。 - Logan
请告诉我这是否有效!在这里,我正在检查最后一个对象的执行。 - Burhanuddin Sunelwala
我看不出你改了什么,但这仍然不会起作用。你不能保证最后一个运行的块是数组中的最后一个对象。 - Logan
是的,我没有改变任何东西。我明白你的意思了。正在尝试另一种方法。顺便说一下,谢谢。 - Burhanuddin Sunelwala
看起来@David的解决方案是正确的!再次感谢! - Burhanuddin Sunelwala

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接