Skip to content

iOS Design Patterns

Mingming Wang edited this page Oct 21, 2013 · 34 revisions

Summary of Design Patterns

Category Design Patterns
Creational Singleton, Abstract Factory
Structural MVC, Decorator, Adapter, Facade, Composite
Behavioral Observer, Memento, Chain of Responsibility, Command, Delegate (?)

Singleton

Sample in CocoaTouch

   [UIApplication sharedApplication];
   [UIScreen mainScreen];
   [NSFileManager defaultManager];

Implementation in Objective C

+ (id)sharedInstance
{
  static dispatch_once_t pred = 0;
  __strong static id _sharedObject = nil;
  dispatch_once(&pred, ^{
    _sharedObject = [[self alloc] init]; // or some other init method
  });
  return _sharedObject;
}

Abstract Factory

Sample in CocoaTouch

Implementation in Objective C

MVC

Sample in CocoaTouch

UIView is the view, UIViewController is the controller and custom data objects are the model.

Implementation in Objective C

In a well structured project, every class should belong to one of the three category. We create a group for each and put the classes in each group according to their role in MVC.

MVC in Xcode

We should contain the business logic in model for re-use and keep controller light since it's the part that most un-re-useable.

Facade

The Facade design pattern provides a single interface to a complex subsystem. Instead of exposing the user to a set of classes and their APIs, you only expose one simple unified API. Facade design pattern

Implementation in Objective C

design-patterns-facade-uml.png

Decorator

The Decorator pattern dynamically adds behaviors and responsibilities to an object without modifying its code. It’s an alternative to subclassing where you modify a class’ behavior by wrapping it with another object. In Objective-C there are two very common implementations of this pattern: Category and Delegation.

Implementation in Objective C

Category

When provide data for UITableView, we may add a category TableRepresentation to the data models. As illustrated in following code:

// Album+TableRepresentation.h 
- (NSDictionary*)tr_tableRepresentation;
// Album+TableRepresentation.m
- (NSDictionary*)tr_tableRepresentation
{
    return @{@"titles":@[@"Artist", @"Album", @"Genre", @"Year"],
             @"values":@[self.artist, self.title, self.genre, self.year]};
}

Delegation

We may also use delegate to give our class a new behaviors or responsibilities. The process is simple, we adopt the delegate then implement the methods in it. A good example is when we use UITableView, the UIViewController can adopt the UITableViewDataSource and UITableViewDelegate and implement the methods in both protocol accordingly.

Adapter

In computer programming, the adapter pattern (often referred to as the wrapper pattern or simply a wrapper - an alternative naming shared with the Decorator pattern according to the GoF Design Patterns book ) is a design pattern that translates one interface for a class into a compatible interface. An adapter allows classes to work together that normally could not because of incompatible interfaces, by providing its interface to clients while using the original interface.

http://en.wikipedia.org/wiki/File:ObjectAdapter.png Adapter_realexample

Implementation in Objective C

When writing a customized control in Objective C, we may include some built-in control such as UIScrollView in the customized control based on need. After that, we can write some interface for the client but inside just use the built-in control to complete the tasks.

Putted in another way, when we design our own class, we may mimic and use the design of some built-in class. Such as UITableView and its delegate. On top of these, we specify our own interface and delegate (with similar style to the built-in ones) and pass the request received to the methods on the built-in classes.

Observer

In the Observer pattern, one object notifies other objects of any state changes. The objects involved don’t need to know about one another – thus encouraging a decoupled design. This pattern’s most often used to notify interested objects when a property has changed.

The usual implementation requires that an observer registers interest in the state of another object. When the state changes, all the observing objects are notified of the change. Apple’s Push Notification service is a global example of this.

Implementation in Objective C

If you want to stick to the MVC concept (hint: you do), you need to allow Model objects to communicate with View objects, but without direct references between them. And that’s where the Observer pattern comes in. Cocoa implements the observer pattern in two familiar ways: Notifications and Key-Value Observing (KVO).

Notifications

Register

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadImage:) name:@"BLDownloadImageNotification" object:nil];

Un-register

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Post notification

[[NSNotificationCenter defaultCenter] postNotificationName:@"BLDownloadImageNotification"
                                                    object:self
                                                  userInfo:@{@"imageView":coverImage, @"coverUrl":albumCover}];

KVO

Add observer

[coverImage addObserver:self forKeyPath:@"image" options:0 context:nil];

Remove observer

- (void)dealloc
{
    [coverImage removeObserver:self forKeyPath:@"image"];
}

Listen to the change

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"image"])
    {
        [indicator stopAnimating];
    }
}

Memento

The memento pattern captures and externalizes an object’s internal state. In other words, it saves your stuff somewhere. Later on, this externalized state can be restored without violating encapsulation; that is, private data remains private.

Implementation in Objective C

We may use NSDefault and Archiving(NSCoding) to implement this pattern.

NSDefault

- (void)saveCurrentState
{
    // When the user leaves the app and then comes back again, he wants it to be in the exact same state
    // he left it. In order to do this we need to save the currently displayed album.
    // Since it's only one piece of information we can use NSUserDefaults.
    [[NSUserDefaults standardUserDefaults] setInteger:currentAlbumIndex forKey:@"currentAlbumIndex"];
}
 
- (void)loadPreviousState
{
    currentAlbumIndex = [[NSUserDefaults standardUserDefaults] integerForKey:@"currentAlbumIndex"];
    [self showDataForAlbumAtIndex:currentAlbumIndex];
}

Archiving(NSCoding)

Adopt the protocol

@interface Album : NSObject <NSCoding>

Implement the protocol

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:self.year forKey:@"year"];
    [aCoder encodeObject:self.title forKey:@"album"];
    [aCoder encodeObject:self.artist forKey:@"artist"];
    [aCoder encodeObject:self.coverUrl forKey:@"cover_url"];
    [aCoder encodeObject:self.genre forKey:@"genre"];
}
 
- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self)
    {
        _year = [aDecoder decodeObjectForKey:@"year"];
        _title = [aDecoder decodeObjectForKey:@"album"];
        _artist = [aDecoder decodeObjectForKey:@"artist"];
        _coverUrl = [aDecoder decodeObjectForKey:@"cover_url"];
        _genre = [aDecoder decodeObjectForKey:@"genre"];
    }
    return self;
}

Save the objects

- (void)saveAlbums
{
    NSString *filename = [NSHomeDirectory() stringByAppendingString:@"/Documents/albums.bin"];
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:albums];
    [data writeToFile:filename atomically:YES];
}

Load the objects

- (id)init
{
    self = [super init];
    if (self) {
        NSData *data = [NSData dataWithContentsOfFile:[NSHomeDirectory() stringByAppendingString:@"/Documents/albums.bin"]];
        albums = [NSKeyedUnarchiver unarchiveObjectWithData:data];
        if (albums == nil)
        {
            //...
        }
    }
    return self;
}

Command

The Command design pattern encapsulates a request or action as an object. The encapsulated request is much more flexible than a raw request and can be passed between objects, stored for later, modified dynamically, or placed into a queue.

Implementation in Objective C

Apple has implemented this pattern using the Target-Action mechanism and Invocation.

Use a variable to store the commands

// We will use this array as a stack to push and pop operation for the undo option
NSMutableArray *undoStack;

Store the commands

 
 NSMethodSignature *sig = [self methodSignatureForSelector:@selector(addAlbum:atIndex:)];
 NSInvocation *undoAction = [NSInvocation invocationWithMethodSignature:sig];
 [undoAction setTarget:self];
 [undoAction setSelector:@selector(addAlbum:atIndex:)];
 [undoAction setArgument:&deletedAlbum atIndex:2];
 [undoAction setArgument:&currentAlbumIndex atIndex:3];
 [undoAction retainArguments];
 
 [undoStack addObject:undoAction];

Retrieve and execute the command

if (undoStack.count > 0){
  NSInvocation *undoAction = [undoStack lastObject];
  [undoStack removeLastObject];
  [undoAction invoke];
} 

Composite

Chain of Responsibility(Responder Chain)

Delegate (?)

When design a shared library for re-use, we may delegate important client side specific parameters or behavors to a delegate of the library which usually is the client itself. The UITableViewDataSource and UITableViewDataDelegate of UITableView is a good sample of delegate pattern.

References