3

Using AFNetworking, I need to download ~100 images in the background and store them to disk while ensuring any other network connectivity in my app takes precedence.

~~~~~~~

I've got an application that has 4 tabs. Each tab basically does the same thing: Pulls down a JSON response from a server, and displays a thumbnail grid of images, pulling down each image on demand (using AF's ImageView category). Tapping on a thumbnail takes you to a detail view controller where you see a larger image. The response and images are different for each tab.

There's a new requirement to fetch all of the images for the 4th tab ahead of time, so that theoretically by the time the user taps on the 4th tab, the JSON data and images are being read from disk.

I've got this working more or less right now, with the 4th tab pre-fetching and saving to disk being performed on a background thread so the main thread doesn't lock up. However, the network requests being kicked off when a user is on the 1st, 2nd or 3rd tab are being blocked by the pre-fetching network requests.

I'm using AFNetworking, and here is the code I'm using when Tab 1, 2 or 3 loads:

// this network request ends up getting blocked by the network request that
// is fired upon the application becoming active
- (void)getAllObjectDataWithBlock:(AFCompletionBlockWrapper)block
{
    [[[MyAPIClient] sharedClient] getPath:@"" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        block(operation, responseObject, nil);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        block(operation, nil, error);
    }];
}

And here is the code I'm using when my application becomes active, to pre-fetch content for the 4th tab:

// this network request runs in the background, but still blocks requests
// that should have a higher priority
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        NSOperationQueue *imageQueue = [[NSOperationQueue alloc] init];
        [imageQueue setMaxConcurrentOperationCount:8];

        for (NSString *imageURL in self.images) {                    
            NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL imageURL]];

            AFImageRequestOperation *operation = [[AFImageRequestOperation alloc] initWithRequest:request];
            [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
                NSLog(@"success");
            } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                NSLog(@"fail");
            }];

            [imageQueue addOperation:smallOperation];
        }
    });
}

How can I structure things so any network requests kicked off from the main thread interrupt those that were kicked off in the background thread?

djibouti33
  • 12,102
  • 9
  • 83
  • 116

1 Answers1

3

I don't know that you can easily interrupt running operations, unless you want to send them a cancel--but you'd have to look at whether AFImageRequestOperation pays attention to isCancelled.

Have you tried using setQueuePriority? You could start all the pre-fetching requests with a low priority, then add current tab requests with a higher priority. I believe running operations would complete, but once they complete, your higher-priority operations would get scheduled ahead of queued low-priority ones.

Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92
  • 3
    `setQueuePriority` works if all of the operations are in the same operation queue, which if he's using the `UIImageView` category, is not the case. So the solution is to either monkey-patch the category to use the same category as the other requests or use `setThreadPriority`, which works across queue contexts. – mattt Sep 12 '12 at 16:48
  • 1
    the solution I finally went with was to monkey-patch AFNetworking's UIImageView category to expose af_sharedImageRequestOperationQueue so it's publicly accessible, as well as setting the queuePriority (NSOperationQueuePriorityVeryLow) and threadPriority (0.25) of all my specific operations I need running in the background. Unfortunately just setting queuePriority left my app unresponsive until all background processing was complete, so I had to do all three. – djibouti33 Sep 12 '12 at 17:22