-
Notifications
You must be signed in to change notification settings - Fork 1
iOS Design Patterns
Category | Design Patterns |
---|---|
Creational | Singleton, Abstract Factory |
Structural | MVC, Decorator, Adapter, Facade, Composite |
Behavioral | Observer, Memento, Chain of Responsibility, Command, Delegate (?) |
[UIApplication sharedApplication];
[UIScreen mainScreen];
[NSFileManager defaultManager];
+ (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;
}
UIView is the view, UIViewController is the controller and custom data objects are the model.
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.
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.
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.
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.
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]};
}
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.
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.
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.
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.
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).
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}];
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];
}
}
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.
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;
}
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.
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:¤tAlbumIndex atIndex:3];
[undoAction retainArguments];
[undoStack addObject:undoAction];
Retrieve and execute the command
if (undoStack.count > 0){
NSInvocation *undoAction = [undoStack lastObject];
[undoStack removeLastObject];
[undoAction invoke];
}
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.