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?
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.
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];
}
});
}