Using Dispatch Groups for Multiple Image Resizing

Three Queues are Better Than One

There are three use cases where I see that image resizing should happen on your phone.

  1. The resized version is the only only version that will be displayed in the app, i.e. Instragram.
  2. You want to do the user a favor and not try and send a full resolution image over the wire to your server. For example, lets say a user has or takes an image that is 2448 × 3264. You would have to have a compelling case for using the bandwhich needed for uploading that size image to resize it.
  3. Use case dictates the need for full resolution images.

Why You Should Have Multiple Sizes

Bandwidth, like death, is the debt that all people must pay for and there is a LOT of competition for this bandwidth (Netflix, Facebook, YouTube, etc). The strategy that I employee for dealing with photos in my apps are as follows.

Assuming their is functionality for some sort of a gallery

iPhone Only App

As stated previously there are rare exceptions where your app should be sending up full resolution images, so I generally resize the image to max of 1024 x 768. This will reduce the file size by more than half while still leaving plenty of pixels left over for proper resizing. On the server side of things the each photo object will have three, sometimes four, different sizes:

  1. Original (the 1024 x 768) version that was uploaded
  2. Fullsize display
  3. Thumbnail
  4. Preview (optional - depends on the UI)

Just as you should be concerned with bandwidth going out, you should be more concerned about content being downloaded. By requesting the size that you need, only when you need it, saves you CPU, GPU, memory, battery, etc. while providing your user with a much better experience. Throw in a sprinkle of FastImageCache and you’ll have happy users. These are not revolutionary concepts, but still basic principles that aren’t too many times overlooked.

iPad Only App

I follow the same basic approach as dealing with an iPhone only application, but I do generally send up the raw image, not because I’m lazy, but because more times than not iPads are used with Wifi so bandwidth, while still a consideration due to horrible public wifi connections, isn’t as much a concern. The one addition that I do for the iPad is make sure that there is some sort of offiline queue that will handle stop/starts of network connectivity and poor connections. NSURLSession makes this so much easier.

Universal App

There isn’t a hard and fast rule to go by, like the “iOnly” applications, but if the goal is to provide a seamless experience with interacting with photos, I do upload the full resolution image from both devices and let the server resize the images to the following sizes:

  1. Original version
  2. Fullsize for iPhone display
  3. Fullsize retina and non-retina for iPad
  4. Fullsize retina and non-retina for iPad
  5. Thumbnail for iPhone
  6. Thumbnail retina and non-retina for iPad

Universal apps take the most tweaking because of all the different use cases and business rules.

Best Laid Plans of Mice and Developers

It is an unfortuante reality for developers that best practices end up becoming good suggestions. For a new feature in GiftsHD2 we are adding in some photo features. Unfortunately, the resizing and resampling that I need to perform can’t be done on the server so my hand is forced. The tasks that need to happen in parallel are creating an “original image”, gallery display image and thumbnail image.

This is iPhone only for now

Group Dispatch

In order to provide the paralleism for the resizing and then a final push of all the images to the server I went with using dispatch_group_async.

Workflow

dispatch_group_async(__imageResizeDispatchGroup, __imageResizeDispatchQueue, ^{...resizedOriginal...});

dispatch_group_async(__imageResizeDispatchGroup, __imageResizeDispatchQueue, ^{...galleryDisplay...});

dispatch_group_async(__imageResizeDispatchGroup, __imageResizeDispatchQueue, ^{...thumbnail...});

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
dispatch_group_notify(__imageResizeDispatchGroup, globalQueue, ^{..save to backend});

IMPORTANT At first glance you might tempted to resize like this:

UIImage * (^resizePhotoForSize)(UIImage *, CGSize) = ^ UIImage* (UIImage *originalImage, CGSize newSize) {

  UIImage *resizedPhoto  = nil;

  UIGraphicsBeginImageContext(newSize);

  [originalImage drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];

  resizedCoverPhoto = UIGraphicsGetImageFromCurrentImageContext();

  UIGraphicsEndImageContext();

  return resizedCoverPhoto;
};

Don’t do that. Use the ImageIO framework. If you aren’t sure why then read Bill Dudney’s book on the subject. You’ll be happy you did.

Controller Snippet

Pros and Cons

The solution works great, but there are still some pitfalls that would probably best overcome by using NSOperations, but this does provide an example what can be done using dispatch groups.