使用块跟踪异步连接

时间:2013-05-24 10:21:24

标签: ios cocoa-touch asynchronous block

我正在做大量的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"]];

         }];

2 个答案:

答案 0 :(得分: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;
        });  
    }
}

答案 1 :(得分:1)

有几点想法:

  1. 如果你想下载60张图片,我不建议使用串行队列进行下载(例如,不要使用maxConcurrentOperationCount 1的操作队列,而是使用并发队列。您将需要同步更新以确保您的代码是线程安全的(这可以通过将更新分派给串行队列(例如主队列)来轻松完成,以便最终更新模型),但我不会建议使用串行队列进行下载,因为这样会慢得多。

  2. 如果你想使用NSURLConnection便捷方法,我会建议类似下面的并发操作请求方法(因为它在后台队列中,我正在使用{{1}而不是sendSynchronousRequest),我假设您的图片网址有sendAsynchronousRequestNSArrayimageURLs个对象:

    NSURL

    一些旁白:首先,我使用的是操作队列而不是GCD并发队列,因为能够限制并发度是很重要的。其次,我添加了一个完成操作,因为我认为知道所有下载何时完成是有用的,但如果你不需要,那么代码显然更简单。第三,使用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]; 的基准测试代码是不必要的,但如果您想使用CFAbsoluteTime maxConcurrentOperationCount4进行比较,则仅用于诊断目的。

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

    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]; 的网络请求(或至少一个使用NSOperation方法的网络请求)打开了额外的机会(例如,您可以取消所有这些网络请求如果必须,您可以获得进度更新等。)

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

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