2

我正在做很多 URL 请求(大约 60 个小图像),并且我已经开始异步处理它们。我的代码添加了另一个图像(很少下载东西),然后设置了一个请求。

请求完成后,我希望将“数据”放在最初为其添加的位置,但是,我看不到如何将“imageLocation”传递给块,以便将图像存储在正确的位置。

我已经用下面的第 3 行替换了它似乎可以工作,但我不是 100% 它是正确的(很难判断,因为图像几乎相同)。我还认为可以在声明块的位置传递“imageLocation”。

任何人都可以证实这一点吗?

__block int imageLocation = [allImages count] - 1;

  // Add another image to MArray
  [allImages addObject:[UIImage imageNamed:@"downloading.png"]];
  imageLocation = [allImages count] - 1;

  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
            [request setTimeoutInterval: 10.0];
            [NSURLConnection sendAsynchronousRequest:request
            queue:[NSOperationQueue currentQueue]
            completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {

                     if (data != nil && error == nil)
                     {
                         //All Worked
                         [allImages replaceObjectAtIndex:imageLocation withObject:[UIImage imageWithData:data]];

                     }
                     else
                     {
                         // There was an error, alert the user
                         [allImages replaceObjectAtIndex:imageLocation withObject:[UIImage imageNamed:@"error.png"]];

         }];
4

2 回答 2

1

处理异步方法很痛苦;)

在您的情况下,它保证完成块将在指定的队列上执行。但是,您需要确保队列的最大并发操作数为 1,否则对共享资源的并发访问是不安全的。这是一场经典的比赛http://en.wikipedia.org/wiki/Race_condition。可以使用属性设置 NSOperationQueue 的最大并发操作数。

通常,除非另有说明,否则完成处理程序可以在任何线程上执行。

当使用一个名为“Promises”的概念http://en.wikipedia.org/wiki/Promise_(​​programming)时,处理异步方法变得容易得多。基本上,“承诺”代表将在未来评估的结果——尽管承诺本身是立即可用的。类似的概念被命名为“期货”或“延期”。

在 GitHub 上的 Objective-C 中有一个 Promise 的实现:RXPromise。使用它时,您还可以从处理程序块中安全地访问共享资源。一个实现如下所示:

-(RXPromise*) fetchImageFromURL:(NSString*)urlString queue:(NSOperationQueue*) queue
{
    @autoreleasepool {
        RXPromise* promise = [[RXPromise alloc] init];

        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
        [NSURLConnection sendAsynchronousRequest:request
                                           queue:queue
                               completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                                   if (data != nil) {
                                       [promise fulfillWithValue:data];                                   
                                   }
                                   else { // There was an error
                                       [promise rejectWithReason:error];
                                   };
                               }];
        return promise;
    }
}

然后调用它:

- (void) fetchImages {

    ...

    for (NSUInteger index = 0; index < N; ++index)
    {
        NSString* urlString = ...
        [self fetchImageFromURL:urlString, self.queue]
        .then(^id(id data){
            [self.allImages replaceObjectAtIndex:index withObject:[UIImage imageWithData:data]];
            return @"OK";
        },
        ^id(NSError* error) {
            [self.allImages replaceObjectAtIndex:index withObject:[UIImage imageNamed:@"error.png"]];
            return error;
        });  
    }
}
于 2013-05-24T17:37:48.487 回答
1

几个想法:

  1. 如果要下载 60 张图像,我不建议使用串行队列进行下载(例如,不要使用带有maxConcurrentOperationCountof的操作队列1),而是使用并发队列。您将希望同步更新以确保您的代码是线程安全的(这很容易通过将更新分派到串行队列(例如主队列)来完成模型的最终更新),但我不会t 建议对下载本身使用串行队列,因为这样会慢得多。

  2. 如果您想NSURLConnection使用方便的方法,我建议您使用以下并发操作请求方法(因为它在后台队列中,所以我使用sendSynchronousRequest而不是sendAsynchronousRequest),我假设您有一个NSArray, imageURLs, ofNSURL图片 URL 的对象:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 4;
    
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"all done %.1f", CFAbsoluteTimeGetCurrent() - start);
        NSLog(@"allImages=%@", self.allImages);
    }];
    
    [imageURLs enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
        NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSURLResponse *response = nil;
            NSError *error = nil;
            NSData *data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:&response error:&error];
            if (!data) {
                NSLog(@"%s sendSynchronousRequest error: %@", __FUNCTION__, error);
            } else {
                UIImage *image = [UIImage imageWithData:data];
                if (image) {
                    dispatch_sync(dispatch_get_main_queue(), ^{
                        [self.allImages replaceObjectAtIndex:idx withObject:image];
                    });
                }
            }
        }];
        [queue addOperation:operation];
        [completionOperation addDependency:operation];
    }];
    [[NSOperationQueue mainQueue] addOperation:completionOperation];
    

    一些旁白:首先,我使用的是操作队列而不是 GCD 并发队列,因为能够限制并发程度很重要。其次,我添加了一个完成操作,因为我认为知道所有下载何时完成会很有用,但如果您不需要,代码显然更简单。第三,使用的基准测试代码CFAbsoluteTime是不必要的,但如果您想比较使用 a maxConcurrentOperationCountof41.

  3. 比使用上述NSURLConnection便捷方法更好,您可能希望使用NSOperation基于 - 的网络请求。您可以编写自己的,或者更好地使用经过验证的解决方案,例如AFNetworking。这可能看起来像:

    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"all done %.1f", CFAbsoluteTimeGetCurrent() - start);
        NSLog(@"allImages=%@", self.allImages);
    }];
    
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    manager.responseSerializer = [AFImageResponseSerializer serializer];
    manager.operationQueue.maxConcurrentOperationCount = 4;
    
    [imageURLs enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
        NSOperation *operation = [manager GET:[url absoluteString] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
            [self.allImages replaceObjectAtIndex:idx withObject:responseObject];
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"%s image request error: %@", __FUNCTION__, error);
        }];
        [completionOperation addDependency:operation];
    }];
    [[NSOperationQueue mainQueue] addOperation:completionOperation];
    

    因为 AFNetworking 将这些完成块分派回主队列,这解决了同步问题,同时仍然享受并发网络请求。

    但是这里的主要信息是NSOperation基于 - 的网络请求(或至少一个使用NSURLConnectionDataDelegate方法的)打开了额外的机会(例如,如果必须,您可以取消所有这些网络请求,您可以获得进度更新等。 )。

  4. 坦率地说,通过两种解决方案来说明如何预先有效地下载图像,我不得不指出这是一个本质上低效的过程。我可能会建议一个“惰性”图像加载过程,以即时(又称“惰性”)方式在需要时异步请求图像。最简单的解决方案是使用UIImageView类别,例如AFNetworkingSDWebImage提供的类别。(如果您已经将 AFNetworking 用于其他目的,我会使用 AFNetworking,但我认为 SDWebImage 的UIImageView类别更强一些。)这些不仅可以无缝地异步加载图像,而且还提供了许多其他优势,例如缓存等有效的内存使用等。而且,它'

    [imageView setImageWithURL:url placeholder:[UIImage imageNamed:@"placeholder"]];
    

关于有效执行网络请求的一些想法。我希望这会有所帮助。

于 2013-11-11T04:37:45.050 回答