Download images in order with AFNetworking

2019-08-30 08:44发布

问题:

How do you download images in order with AFNetworking? An by "in order", I also mean executing the success blocks in order.

Initially I thought it would be enough to use a NSOperationQueue and set each AFImageRequestOperation as a dependency of the next one. Like this:

- (void) downloadImages
{
    { // Reset
        [_downloadQueue cancelAllOperations];
        _downloadQueue = [[NSOperationQueue alloc] init];
        _images = [NSMutableArray array];
    }
    AFImageRequestOperation *previousOperation = nil;
    for (NSInteger i = 0; i < _imageURLs.count; i++) {
        NSURL *URL = [_imageURLs objectAtIndex:i];
        NSURLRequest *request = [NSURLRequest requestWithURL:URL];
        AFImageRequestOperation *operation = [AFImageRequestOperation 
                                              imageRequestOperationWithRequest:request 
                                              imageProcessingBlock:nil 
        success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
            [_images addObject:image];
            NSLog(@"%d", i);
        } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];

        if (previousOperation) {
            [operation addDependency:previousOperation];
        }
        previousOperation = operation;

        [_downloadQueue addOperation:operation];
    }
}

This prints i in order when the images are downloaded. However, when the requests are already cached, the success blocks are processed out of order. I suspect this is a NSOperation limitation, not AFNetworking.

Am I missing something?

回答1:

As a workaround, I store the images in a dictionary and process them in order each time a request succeeds. Like this:

- (void) downloadImages
{
    { // Reset
        [_downloadQueue cancelAllOperations];
        _downloadQueue = [[NSOperationQueue alloc] init];
        _images = [NSMutableArray array];
        _imageDictionary = [NSMutableDictionary dictionary];
    }
    AFImageRequestOperation *previousOperation = nil;
    for (NSInteger i = 0; i < _imageURLs.count; i++) {
        NSURL *URL = [_imageURLs objectAtIndex:i];
        NSURLRequest *request = [NSURLRequest requestWithURL:URL];
        AFImageRequestOperation *operation = [AFImageRequestOperation 
                                              imageRequestOperationWithRequest:request 
                                              imageProcessingBlock:nil 
        success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
            [_imageDictionary setObject:image forKey:@(i)];
            [self processImages];
        } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];

        if (previousOperation) {
            [operation addDependency:previousOperation];
        }
        previousOperation = operation;

        [_downloadQueue addOperation:operation];
    }
}

- (void) processImages 
{
    for (NSInteger i = _images.count; i < _imageURLs.count; i++) {
        UIImage *image = [_imageDictionary objectForKey:@(i)];
        if (!image) break;
        [_images addObject:image];
        NSLog(@"%d", i);
    }
}

This always prints i in order.



回答2:

Your solution will work fine, here is another way to do it:

For the "perfect" UX you should issue all operations in parallel and process images by order as they come (don't wait if you don't have to).
(error handling is done differently here)
You could try this (untested, and you can better design the model [don't just use arrays like this]):

- (void) processImage:(UIImage*)image
{
    //do something with the image or just [_images addObject:image]
}

- (void) downloadImages
{
    { // Reset
        [_downloadQueue cancelAllOperations];
        _downloadQueue = [[NSOperationQueue alloc] init];
    }

    __block NSMutableArray* queue = [[NSMutableArray alloc] initWithCapacity:[_imageURLs count]];

    for (NSURL* url in _imageURLs) {
        __block NSLock* lock = [[NSLock alloc] init];
        __block NSMutableArray* container = [NSMutableArray new];
        [lock lock];
        [queue addObject:@[lock,container,url]];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        void(^compBlock)(NSURLRequest *request,
                         NSHTTPURLResponse *response,
                         UIImage *image) = ^(NSURLRequest *request,
                                             NSHTTPURLResponse *response,
                                             UIImage *image)
        {
            [container addObject:image];
            [lock unlock];
        };
        NSOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request
                                                                      imageProcessingBlock:nil
                                                                                   success:compBlock
                                                                                   failure:compBlock];
        [_downloadQueue addOperation:operation];
    }

    __block __weak id weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (NSArray* arr in queue) {
            NSLock* lock = arr[0];
            [lock lock];
            NSMutableArray* container = arr[1];
            if ([container count]) {
                [weakSelf processImage:container[0]]; //might want to call this on main thread
            } else {
                //error on url = arr[2]
            }
            [lock unlock];
        }
    });    
}