Objective-C Runtime with Swift

One thing that I really miss when using Swift is Objective-C’s runtime. I don’t go crazy with it, but its power, when used wisely, is highly effective.

I had an idea a few weeks ago for a fun little app and thought this would be a great time to build an app from the ground up using Swift. I have a lot of convenience classes and logic written in Objective-C that needed to be ported over. One the first, and most important categories, is on UIView. I extend the class to include the ability to add a tap gesture.

Over the years I’ve moved over to use Oliver Drobnik’s technique. It is cleaner than my original approach.

When Swift was announced, Apple assured developers that you could mix and match Objective-C and Swift. Most things you can. One of the skeptisms I originally had concerned the runtime. Mattt Thompson posted a great piece on NSHipster that gives some great examples on how to do it. I used that article as the main reference when starting to port my category over. When I went to compile the project I got the following error.

error: type ‘() -> Void’ does not conform to protocol ‘AnyObject’

var tapAction: (() -> Void)? {

    get {
        objc_getAssociatedObject(self, &AssociatedKeys.SNGLSActionHandlerTapBlockKey)
    }

    set {

        objc_setAssociatedObject(
            self,
            &AssociatedKeys.SNGLSActionHandlerTapBlockKey,
            newValue,
            UInt(OBJC_ASSOCIATION_COPY_NONATOMIC)
        )
    }
}

I wrestled with this for quite sometime. I understood what it was saying, but didn’t have an elegant solution for it or hell an unelegant solution. Part of my problem was that I was thinking in terms of Objective-C and not Swift. After posting to StackOverflow Greg Heo pointed me in the right direction. How do make pass in a Void type into something that must conform to AnyObject…you have to wrap it in a class.

Well that is simple enough:

class ClosureWrapper {
  var closure: (() -> Void)?

  init(closure: (() -> Void)?) {
    self.closure = closure
  }
}

var tapAction: (() -> Void)? {
  get {
    if let cl = objc_getAssociatedObject(self, "key") as ClosureWrapper {
      return cl.closure
    }
    return nil
  }

  set {
    objc_setAssociatedObject(
      self,
      "key",
      ClosureWrapper(newValue),
      UInt(OBJC_ASSOCIATION_COPY_NONATOMIC)
    )
  }
}

Not so fast. That is the right concept, but there is some additional work that you have to do. The ClosureWrapper needs to inherit from NSObject and conform NSCopying.

Now I’m able to add a tap gesture to any UIView subclass.

Personal Project Preferences for IOS

It’s Personal

How developers setup their projects is a very personal subject and can invoke extreme emotion if ever critized. While I’m pretty set in my ways as to how I like to setup my iOS projects, I know that I’ve gotten to my preferred setup because of others so I would like to share mine.

Workspaces and Projects

When starting a new iOS project I always create a new workspace. Most of my projects are not just a singular project and workspaces provide the best relationship management, whether it is a static library, framework or related.

Filesystem

Since I edit the classes in the Xcode and do not access them via the file system I don’t create any additional folders or hierarchy inside the project(s). It doesn’t make seems to me. Any class organization I handle with groups.

With the addition of asset folders in Xcode, image assets are now kept within the project as well. The file system project folders are split between the various “source” projects, configurations, external libraries and misc dependencies.

Configurations

It is a huge pet peeve when I see code like this:

NSString *serverURL = nil;
#if DEBUG
serverURL = @"https://path.to.dev/";
#else
serverURL = @"https://path.to.prod/";
#endif

These type of environment key/values shouldn’t be determined by preprocessor macros inside of your app. Even logging can/should be handled by a dedicated class. Since my projects usually have three build configurations, Debug, Adhoc and Release, I’ll create three plists: ConfigDevelopment, ConfigAdhoc and ConfigRelease.

You can create as many or few as you want depending on your setup

I don’t need all three of these plists in the app’s bundle, only the appropriate one which corresponds to my current build configuration. This is handled by a build script

#!/bin/sh

PATH_TO_CONFIGURATIONS="$SRCROOT/../../Configurations"
ConfigDevelopment="$PATH_TO_CONFIGURATIONS/ConfigDevelopment.plist"
ConfigRelease="$PATH_TO_CONFIGURATIONS/ConfigRelease.plist"
ConfigAdhoc="$PATH_TO_CONFIGURATIONS/ConfigAdhoc.plist"

echo "Checking for file $ConfigDevelopment, $ConfigRelease, $ConfigAdhoc..."

if [ $CONFIGURATION == "Debug" ]; then
echo "Processing $CONFIGURATION"
if [ -f "$ConfigDevelopment" ]; then
cp "$ConfigDevelopment" "$PATH_TO_CONFIGURATIONS/Config.plist"
else
echo "$ConfigDevelopment not found"
exit 1
fi
fi

if [ $CONFIGURATION == "Adhoc" ]; then
echo "Processing $CONFIGURATION"
if [ -f "$ConfigAdhoc" ]; then
cp "$ConfigAdhoc" "$PATH_TO_CONFIGURATIONS/Config.plist"
else
echo "$ConfigAdhoc not found"
exit 1
fi
fi

if [ $CONFIGURATION == "Release" ]; then
echo "Processing $CONFIGURATION"
if [ -f "$ConfigRelease" ]; then
cp "$ConfigRelease" "$PATH_TO_CONFIGURATIONS/Config.plist"
else
echo "$ConfigRelease not found"
exit 1
fi
fi

if [ -f "$PATH_TO_CONFIGURATIONS/Config.plist" ]; then
echo "Processing $CONFIGURATION"
echo "Found Config.plist, and copying to $TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH"
cp "$PATH_TO_CONFIGURATIONS/Config.plist" "$TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH/"
else
echo "Did not find Config.plist"
exit 1
fi

In my projects I create a simple NSObject subclass, AppConfig which loads the Config.plist from the bundle and sets the values from the plist’s dictionary. Though I haven’t done it yet, you could “automate” this process further by using some Objective-C runtime to dynamically create and set the properties.

WIth this setup whatever setting, URL, path, etc. I can grab by calling [AppConfig defaultConfig] without having to maintain or deal with unnecessary runtime settings.

Subclassing

I spend a lot of time and energy evaluating DRY in my code. This isn’t only applied to abstraction, but inheritenance. View controllers and views (standard UIView subclasses, UILabels, UIButtons, etc) will share some basic default attributes that I only want to set once, and by design, modify once to be applied to all. You can accomplish a lot on the UI side of things by using UIAppearance, which I do utilize, but anything outside of that supported by the protocol I have a base class.

For example, this is what my base UIViewController would look like.

- (instancetype)init {
    
    self = [super init];
    
    if (self) {

        self.edgesForExtendedLayout           = UIRectEdgeNone;
        self.extendedLayoutIncludesOpaqueBars = YES;
    }
    
    return self;
}

// MARK: View Life Cycle

- (void)loadView {
    
    self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.view.backgroundColor = [UIColor whiteColor];
}

There isn’t much to look at, but in a fairly large application having type/copy/paste that becomes tedious and unnecessary. Now I just have to subclass this base controller.

In the case of UIView and UIControl subclasses, I almost exclusively use AutoLayout and I don’t use Storyboards or XIBs. I create a base AutoLayout view and button class and set the AutoLayout “flag”. Labels are given some extra love by adding an additional property so that I can handle multiline and edgeInsets.

// MARK: Initializers

- (instancetype)initWithFrame:(CGRect)frame {
    
    self = [super initWithFrame:frame];
    
    if (self) {
        
        _edgeInsets = UIEdgeInsetsZero;
        
        self.translatesAutoresizingMaskIntoConstraints = NO;
        self.textColor       = [UIColor whiteColor];
        self.backgroundColor = [UIColor clearColor];
        self.font            = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    }
    
    return self;
}

- (instancetype)init {
    return [self initWithFrame:CGRectZero];
}

/**
 * @discussion
 * In order for autolayout (or sizeThatFits) to correctly calculate the size of the
 * label when using edgeinsets then we must adjust the rect accordingly.
 *
 * If not then you can run into the situation where multiline text is cut off.
 * This happens when calculating dynamic size height cells in UICollectionView's.
 */

- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines {

    UIEdgeInsets insets = self.edgeInsets;
    CGRect rect = [super textRectForBounds:UIEdgeInsetsInsetRect(bounds, insets)
                    limitedToNumberOfLines:numberOfLines];
    
    rect.origin.x    -= insets.left;
    rect.origin.y    -= insets.top;
    rect.size.width  += (insets.left + insets.right);
    rect.size.height += (insets.top + insets.bottom);
    
    return rect;
}

- (void)drawTextInRect:(CGRect)rect
{
    [super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.edgeInsets)];
}

MVCUC,e (Models, Views, Controllers, Utilies, Categories, etc)

I like to take the abstraction of responsibility a bit further using my own Foundation framework project added to the workspace that holds all the models, UIView parent classes, utilies and categories. The app project itself is mainly view controllers, static assets and config files. This type of setup wasn’t as necessary early on in iOS development, but as devices and functionality (Apple Watch, separate iPad and/or Mac apps and extensions) are added it has been extremely beneficial to keep these separate.

Daniel Kennet’s article talks about the benefits of taking a similar approach.

Basically, if it doesn’t involve UI or application state, it goes in a separate framework. My iPod-scanning app Music Rescue contained a complete framework for reading an iPod’s database. My pet record-keeping app Clarus contained one to work with the application’s document format.

Dependencies

This is probably the most debatible preference I have. I do not like CocoaPods. I prefer to use git submodules. I admire and respect the work that has gone into CocoaPods and there are certainly benefits to using it, but it has caused more headaches and is one more external dependency that I have to work with. Ruby (I know it is installed by default), messing with pod files, the XCode project modifications to name a few. Git submodules aren’t without their issues, but you have complete visbility into what is going on. I don’t feel that way about CocoaPods. I’ve used it on a few projects at different points over the past year and it just annoys me.

Networking

My network stack is homegrown solution, which consists of a Client, Request and Session class. The client has paginator property.


------------------------------------------------- =====================
Client                                              paginator
------------------------------------------------- =====================
------------------------------------------------
Request
------------------------------------------------
------------------------------------------------
Session
------------------------------------------------

The only class and methods that are accessed directly by the app is the Client instance which calls down the Request class (handles get, put, post and delete verbs) which in turns calls down the Session (wrapper around NSURLSessions).

The client has a property called paginator which allows the Client instance to call nextPage or previousPage for request pagination.

@property (nonatomic, weak) RTVPaginator *paginator;

XIB’s, Storyboards and Views

Unless it is a requirement I DO NOT use XIB’s or Storyboards. Over the course my iOS career, when I did use them, it became yet another asset to manage and I found myself, more times than not, doing all of my customizations programmatically and never really touched the XIB’s after their initial creation so I just quite using them all together. With the addition of AutoLayout in iOS6 all of the initial tutorials and examples used XIB’s so I gave it another try. To me it was MORE frustrating dealing with AutoLayout using IB so I started doing all of the setup programmatically and haven’t had any issues.

XCode Build Settings

Before I add/modify in any project I’ll create target specific xcconfig files. One shared and specific ones for the various build settings. Tweaking specific settings becomes much more manageable that way, in addition, to see change sets via diff tools. Viewing the project files in any diff viewer is a horrible experience.

The Way

The Right Way, The Wrong Way and Your Way

There is no really wrong to setup or maintain your project. If you are developing and shipping apps then your setup is correct. Over the past 7 years certain trends have emerged as common expectations, but I have yet to see an official recommendations or guidelines from Apple so I’ll (and you) should keep doing it your way.

I would love to hear more of how you manage your project setup.

Theo - An Open Source Neo4j Framework

Who?

Thomas Anderson is a computer programmer who maintains a double life as “Neo” the hacker. - Combination of Neo and Thomas

What?

Theo is an open-source framework written in Swift that provides an interface for interacting with Neo4j.

Why?

The why comes from multiple reasons.

1. Swift

Over the past few months I’ve been reading and writing some small utilities and getting my feet weight with Swift. I’ve found myself really enjoying the language and wanted to do something on a larger scale. This seemed like the next logical step for me to get a better grasp on the language and it’s possibilities. It didn’t disappoint in its challenges and rewards.

2. Neo4j

Neo4j is a highly scalable, robust (fully ACID) native graph database. Neo4j is used in mission-critical apps by thousands of leading startups, enterprises, and governments around the world. - http://neo4j.com/guides/about-neo4j/

The most common usage for graph’s is social data sets, but it should never be assumed that only social apps/services can use Neo4j. Business intelligence, content management, recommendation engines, and geo are just a few of areas of it superiority over traditional RDBMs. Customers and case studies.

My introduction to graph databases was from my good friends over at FatFractal. I saw the power and flexibility that leveraging this technology had on the variety of data that one could present. I had read a few tutorials about Neo4j, but never had the opprotunity to really dig deeper. As the need to show complex relationships in a straightforward and performant way for me grew I kept coming back to Neo4j and what it provided.

3. Challenges

Everything about the Theo was a challenge. Using a new language that has been officially 1.0 for ~month, using new Xcode features and wrapping that around Neo4j…a graph database that I’m a novice in. The rewards and excitement of using these technologies has been better than I could imagine. In all honesty I could have written the current version with Objective-C in about a quarter of the time, but I feel the greatest rewards come from the greatest challenges. Swift has a huge part in the future of iOS development and this was the perfect playground, if you will, to use it. The Neo4j API is interesting. In all honesty I don’t believe it is the most consistent API that I’ve used, but there is a ton of flexiblity in it.

  • If you look at the current version there is a lot of logic and modeling to deal with meta information. This was because in 2.1.4 the API kept a lot of it separate. However, yesterday, a maintenance release was pushed out, 2.1.5…the API now includes:

Adds additional metadata (label and ID) to node and relationship representations in JSON responses from the REST API.

KKHHHHHHAAAANNNNNN

I haven’t thrown out the “meta” related structs because of backwards compatibility, but please note that in future releases I will rely more on what the API returns from the nodes themselves as opposed to bridging the two.

4. I Like Working with Friends

Greg

programmer, usability hunter, potential lunch winner - love graph databases. I SAID LOVE THEM

When I worked at St. Jude Greg was my boss for 5 years. Over that time, and ever since, we’ve been good friends. For the past few years, between raising 3 kids with his wife and getting his Ph.D., he has been jetting setting (coach unfortuantely) across the country evagenlising Neo4j. As of a few months ago he started GraphStory and asked if I would like to write a Neo4j client for them. I couldn’t jump on that project fast enough.

Jeremy

PHP dev, motorcyclist, and amateur photographer, CTO/Lead Developer at http://GraphStory.com

When I first started at St. Jude the two main languages of use where Java 1.3 and ASP (shoot me now). Needless to say the Java version need a slight bump, which it got, but the .asp had to go. Our team ended up moving over to a LAMP stack, but that also meant a ton of migration of applications and random forms to PHP. I was working on a custom CMS so we hired Jeremy as a contractor. He was solid with “function” based PHP, but had only a little experience with OO. Over the next few months I showed him a few fundamentals and settled on Zend Framework (1.0) and the next thing I knew he was extending the framework and writing the basis for our in house library that is still in use today. After he left St. Jude he went on the found the MemphisPHP user group and speaking all over the country about PHP. Say what you will about PHP, but the community is pretty awesome and Jeremy has been a big part of that. Now that he is the CTO of GraphStory…I would listen to whatever that man has to say. He has earned his place.

Ready for Production?

The code that is on Github is very stable. I’ve written tests for all the functionalty up to this point. There are still some pieces of functionality that I want to add in, as well as, make the Client object a bit more user friendly. If you are interested in contributing, find a bug or ask a question please view the README for the best way to do that. I’ll take the library as version 1.0 in the next few weeks.

Crowdplace, MVP and Working on 1.1

Crowdplace

For the past few months I’ve been working on the iPhone app for [Crowdplace][http://www.crowdplace.com/]. In a nutshell what Crowdplace does is let you save your favorite content into a customized stream of useful information. Whether it is an RSS feed, Facebook posts, pictures, links, etc. is inconsequential…everything is considered content.

You can read more here

I was interested in working with the team for numerous reasons, but two of the biggest ones where:

  1. I got to work with my friend, and former boss from St. Jude, Greg Jordan
  2. Greg used Neo4j for backend storage.

MVP

When I took on the project the desktop browser client was much much further along than any iOS efforts. Though there wasn’t a hard “due date” to launch the app, there was a general timeframe. Unfortunately I didn’t have the time to get the app up to a 1:1 feature set with the desktop client during that time. I spent about a week going to through and make some pretty hard descisions about what would stay and what would have to wait. Those on the chopping block included features, as well as, some under the cover solutions. For example, there isn’t really robust caching happening outside of NSURLCache and on disk/memory caching for profile images.

Right now version 1.0 is undergoing testing from a variety of users. What I was able to deliever might be “missing” features, but what it does do it does well. I was able to squeeze in background fetch I spent as much time and effort as I could to make sure that the app was smooth and stable. I would rather release an initial version of an app that is feature limited than something that is clunky and crashes.

1.1

While version 1.0 is being tested I’ve already started the 1.1 branch and development is well underway. The first big sprint is syncing and caching using CoreData. Since CoreData will be the caching mechanism I’m having to do quite a bit of refactoring models, callbacks, etc., but I can already see the benefits of these efforts. While my focus for version 1.0 is to provide a stable and easy to use app, I’m even more excited about version 1.1 and the foundation that it will be built on.

ZEMCircleProgressView

ZEMCircle Time

The inspiration for ZEMCircleProgressView was FFCircularProgressView I really liked it’s simplicity and was going to use it in a project but there were some design pattern decisions that bothered me, as well as, some features I needed like cancel and manual start.

My intention was to issue a pull request but by the time I got around to it the original project had changed (for the better) but had grown in complexity that I didn’t want. Huge thanks to Cory Hymel for taking the time to debug a timer issue for me!

Check it out and let me know if there are any bugs or features you would like to see.

Asynchronous Image Loading for UITableViewCells

Simmons, Brent Simmons

Unless you do everything you possibly could to stay away from anything related to iOS development, then you’ve at a bare minimum stumpled upon at least one of Brent Simmons articles on the continued development of Vesper. I try to blog somewhat regularly, but the frequency tends decrease the more involved I am with a project. However, with Mr. Simmons, it seems to be the opposite and I’m inspired to do more of the same. Being a devout reader of his chronicals it has been comforting to know that a developer, such as himself, goes through many of the conumdrums of development decisions that myself and I’m sure many others do. In addition, I think that the fact a) Brent is willing to disclose so much information regarding the development b) The great responses from other developers on his approaches are exemplary of how great the iOS/Mac dev commnuity is. The fact is that iOS development is hard and no matter how good you are the answers aren’t always straightforward and there is always something new and interesting to learn.

Asynchronous Image Loading - Hasn’t This Already Been Solved

How many guitarist does it take to play Stairway to Heaven? 100…1 to actually change it and the other 99 to say, Not bad, but I could do it better

I’ve seen many different ways to handle effective asynchronous image dowloading, specifically in the context of UITableView’s. Most of the time this involves some variation of utilizing Grand Central Dispatch, which in this context isn’t very effecient for few reasons.

  1. Not a good way to manage the active queues
  2. You end putting in way too many references to the cell instance in UIScrollView delegate methods
  3. Your controller code becomes too large for it’s own good.

My Version

I’m not including any caching logic because that is a subject for another blog (debate)

Benefits

  1. Utilizes NSOperation and NSOperationQueues
    • You can have as many operations as you want in your cell subclass
  2. Reference to the cell instance via block param
  3. Using UITableViewDelegate methods to cancel any download operation for cells that aren’t onscreen

PhotoViewerCell.h

@class PhotoViewerCell;
@class Photo;

extern NSString * const  PhotoViewerCellIdentifier;

typedef PhotoViewerCell* (^PhotoViewerCellBlock)(void);

@interface PhotoViewerCell : UITableViewCell

@property (nonatomic, strong) Photo *photo;

- (void)loadPhotoDataForCell:(PhotoViewerCellBlock)currentCell
                   withPhotoInstance:(Photo *)aPhoto
                               queue:(NSOperationQueue *)queue;
- (void)cancelDownload;

PhotoViewerCell.m

#import "PhotoViewerCell.h"
#import "Photo.h"

NSString * const  PhotoViewerCellIdentifier = @"PhotoViewerCellIdentifier";

@interface PhotoViewerCell() 

@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, weak) NSOperation *fullImageRequestOperation;

@end

@implementation PhotoViewerCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
  
  self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
  
  if (self) {
    
    _imageView = [[UIImageView alloc] initWithFrame:frame];
    
    _imageView.contentMode            = UIViewContentModeScaleAspectFit;
    _imageView.clipsToBounds          = YES;

    [self.contentView addSubview:_imageView];
  }

  return self;
}


#pragma mark - Public Methods

- (void)loadPhotoDataForCell:(PhotoViewerCellBlock)currentCell
                   withPhotoInstance:(Photo *)aPhoto
                               queue:(NSOperationQueue *)queue {
  
  NSOperation *fullImageBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
    
    NSString *fullsizeImageUrl = [NSString stringWithFormat:@"%@%@/iPhoneFullsizeImage",
                                                 [FFManager sharedManager].ff.baseUrl, [aPhoto ms_ffUrl]];
    
    NSURL *downloadURL = [NSURL URLWithString:fullsizeImageUrl];
    NSData *imageData      = [NSData dataWithContentsOfURL:downloadURL];
    UIImage *scaledImage = [UIImage imageWithData:imageData scale:[UIScreen mainScreen].scale];
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
      
      PhotoViewerCell *originalCell = (id) currentCell();
      
      originalCell.imageView.image = scaledImage;
    }];
  }];

  self.fullImageRequestOperation = fullImageBlockOperation;
  
  [queue addOperation:self.fullImageRequestOperation];
  
  self.imageView.image = [UIImage imageNamed:@"placeholder"];
}

- (void)cancelDownload {

  if (self.fullImageRequestOperation) {
    MSLog(@"cancelled");
    [self.fullImageRequestOperation cancel];
  }
}

@end

ViewController Snippet


- (void)viewWillDisappear:(BOOL)animated {
  
  [super viewWillDisappear:animated];
  
  [self.imageDownloadQueue cancelAllOperations];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  PhotoViewerCell *cell = [tableView dequeueReusableCellWithIdentifier:PhotoViewerCellIdentifier
                                                           forIndexPath:indexPath];

  PhotoViewerCell * (^currentCellForBlock)(void) = ^PhotoViewerCell*{
    return (id) [tableView cellForRowAtIndexPath:indexPath];
  };
  
  Photo *currentPhoto = (Photo *)self.photos[indexPath.row];
  
  [cell loadPhotoDataForCell:currentCellForBlock
                        withPhoto:currentPhoto
                            queue:self.imageDownloadQueue];
  
  return cell;
}

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
  
  PhotoViewerCell *currentCell = (PhotoViewerCell *)[tableView cellForRowAtIndexPath:indexPath];
  
  [currentCell cancelDownloads];
}

Macspots - The Intersection Between Science and Magic

Macspots

Over the past 6 months I’ve been working with my good friend Jason on revamping Gifts, as well as, collaborating on a some crazy ideas that we’ve talked about over the past few years. To provide more visibility and credenance to the work that we’re doing we’ve created a blog. Our innagural post was published today.

If you have a love/hate relationship with AutoLayout it might be of interest.

Stayed tuned for further snippets of code, lessons learned, and most importantly, amazing apps (at least we think so) over at Macspots…the intersection of science and magic.

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.