Skip to content

iOS Design Patterns, Part 1 – Concurrency & Blocks

June 28, 2012

I’ve been getting a lot of requests lately from folks in the iOS dev community, asking for help with useful design patterns. I’ve been routinely referring them to Joshua Bloch’s Effective Java as the de facto standard in modern software design patterns. Sure, it’s intended for the Java developer audience, but design patterns transcend language, and it covers a lot of ground. For all the wonderful things in that book, though, it was written before the advent of Grand Central Dispatch, so it doesn’t cover any of the marvels and mysteries of queue-based concurrency. This is the first of a series of posts covering design patterns used in iOS development.

Grand Central Dispatch (GCD)

One of the most significant aspects of working with iOS is the availability of a high performance thread management system. Using a succinct C-based function API, Grand Central Dispatch allows the developer to construct discrete chunks of code, called blocks, and organize them in queues for execution. GCD acknowledges the need for a main thread, where user interface components are rendered to the display and user interaction events are handled. It introduces a clean dividing line between main thread and background thread. All tasks that can be executed in the background are dispatched to the global queue. GCD abstracts the threads themselves, since each device may have a different number of cores. It does this at the OS level, too, so each app doesn’t need to implement its own isolated thread pool. The following patterns leverage the power of GCD to produce highly performant and robust apps.

NOTE: For brevity, I use macros (async_main and async_global) to represent the GCD functions. I recommend this unless you need to control priority in the global queue.

Package user interface updates into blocks and dispatch to main thread

The most common situation in any interactive system is the need to accept input from a user, perform some function based on the input, and present output to the user. Sometimes, the input needs to be packaged and sent to a remote system for processing. Clearly, in that case, there needs to be some consideration for the asynchronous nature of the process. The data is not necessarily available immediately. In fact, the best practice is to treat every situation as if it were asynchronous. In other words, even if you’re just calling a method that will take a second or two to return, that must be integrated asynchronously.

Not recommended:

- (IBAction)computeNewPriceAndUpdate
{
  [self computePrice];
  [self updateUI]
}

- (void)computePrice
{
  // lots of computations that mutate price property
}

- (void)updateUI
{
  priceLabel.text = [NSString stringWithFormat:@"$%3.2f",self.price];
}

Suggested alternative:

- (IBAction)computeMetricAndUpdate
{
  async_global(^{
    [self computePrice];
    async_main(^{
      [self updateUI];
    });
  });
}

The outer block is executed on the global queue. First, it calls computePrice, which modifies the price property. Then, it dispatches the inner block to the main thread. This guarantees that the priceLabel object is never accessed by a background thread. It also guarantees the value of the price property is updated and ready to be accessed when updateUI is called.

Pitfall:

- (IBAction)computeMetricAndUpdate
{
  async_global(^{
    [self computePrice];
  });
  async_main(^{
    [self updateUI];
  });
}

This will probably cause the priceLabel to be updated with the original price, not the result after computePrice returns. Once the block is dispatched to a queue, that queue will execute it as soon as possible. With this arrangement, it’s impossible to guarantee correct order of execution.

Use block callbacks to build reusable components

Another very common situation is a view with editable information. Consider a user session workflow. A view is presented to the user, allowing entry of email and password. That data is sent to a remote system for authentication. If the information is invalid, an alert is shown. If valid, the view is dismissed, and a new view is presented. One might consider using a delegate protocol on the session view, and that would certainly solve the problem. However, that introduces a tight coupling. The controller that constructs the session view before presenting it must then inherit the protocol and define the required callback methods.

Not recommended:

@protocol SessionDelegate
-(void)sessionCreated;
-(void)sessionDestroyed;
@end

@interface MyViewController <SessionDelegate>
@end

@implementation MyViewController
- (IBAction)signInPressed
{
  SessionViewController* tSessionView = [SessionViewController new];
  tSessionView.delegate = self;
  [self.navigationController presentModalViewController:tSessionView animated:YES];
}
- (void)sessionCreated {}
- (void)sessionDestroyed {}
@end

Suggested alternative:

- (IBAction)signInPressed
{
  SessionViewController* tSessionView = [SessionViewController new];
  tSessionView.successBlock = ^{
    async_main(^{
      [self.navigationController dismissModalViewControllerAnimated:YES]; 
    });
  };
  [self.navigationController presentModalViewController:tSessionView animated:YES];
}

By using blocks to encapsulate callback behavior, we eliminate the coupling between MyViewController and SessionViewController. Instead of forcing a typed relationship with a protocol and implementation, we inject behavior abstractly as a block. This makes it easier to work with SessionViewController in other situations. An example of alternative usage might be different design for phone and tablet form factors. Maybe the session view is a full-screen modal on the phone, but a popover on the iPad. Using this pattern, we can configure behavior externally and inject it into the view controller before presenting the view.

Pitfall:

@interface SessionViewController {}
@property (strong) dispatch_block_t successBlock; // this will cause problems
@end

Using blocks as properties is a great way to prevent bad access issues. Just make sure you use the copy directive instead of strong or retain in the property definition.

Add default block callback implementations for common use cases

This is more of an extension to the last section. If the most common use case is to present a view modally, then set a default block callback implementation to dismiss the modal view.

Suggested implementation:

@implementation SessionViewController
- (void)viewDidLoad
{
  if( successBlock==nil ) {
    self.successBlock = ^{
      [self.navigationController dismissModalViewControllerAnimated:YES];
    };
  }
}
@end
About these ads
No comments yet

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 93 other followers

%d bloggers like this: