Design patterns for iOS developers. Observer Part I
Instead of the foreword
It's been 17 years since the legendary book of the Gang of Four, dedicated to Design patterns, was released. Despite such a solid period, it is difficult to challenge the relevance of the methods described in it. Design patterns live and evolve. They are used, discussed, scolded and praised. Unfortunately, for many they still remain an unnecessary abstraction.
Discussing various programming issues with colleagues both in life and on various resources, it is often necessary to explain the importance of a particular pattern. And the idea was born to show concrete examples of how their use can make life easier for the programmer. Even when it comes to a platform like iOS.
Observer Pattern
Life example
Student Vasya loves to go to parties. To be more precise, they occupied such an important part of his life that he was expelled from the institute. Having started looking for work, Vasya realized that he knew almost nothing and could not. Although, wait ... Vasya has a lot of beautiful girls who do not feed bread - let them get into a cool party without an invitation. Vasya is also known in all the relevant institutions of his city. Finally, Vasya understands: for the party to succeed (wealthy visitors spent a lot of money), it would be nice to fill it with beautiful girls (who will spend this money on).
So Vasya
A year passed, Sveta graduated from the institute, found a job and, most importantly, got married! She no longer wants to go to parties, so yesterday she called Vasya and asked her not to annoy her anymore with her idiotic messages. So she unsubscribed from the (very suspicious) subject of Vasya. However, she knows that she can always sign up again (she is still pretty, young, well dressed).
Meanwhile, Vasya did not stand still, but expanded his business. Recently, he met several very wealthy football players who like to relax at a party and are always ready to treat pretty girls with a non-alcoholic cocktail. Naturally, they can also be invited to various events (the organizers are delighted, they pay Vasya more money). Business expansion went unnoticed: really, who cares who to call? Both girls and football players have telephones. Sometimes, reporting on a new event, Vasya even confuses whom he is talking to.
Shorter example
Those who closely followed Vasya’s business could immediately recall the newsletters. Indeed, we can leave your e-mail address on your favorite resource and receive
Definition
Gang of Four
- Title: Observer.
- Classification: Behavior Pattern.
- Appointment:
Defines a one-to-many relationship between objects in such a way that when a state of one object changes, all dependent on it are notified about it and are automatically updated.
Comments
So, the observer pattern defines a one-to-many relationship. Moreover, an object that reports about its changes is called a subject , and those objects to which it reports about them are called observers .
It is worth noting an important feature of the observer pattern: the subject may know almost nothing about the observers. So, Vasya did not see any difference between the girls and the players.
Structure
Observer for iOS Developer
Let's move on to programming. First, we will analyze our own implementation of the observer pattern using a specific example, and then we will analyze the notification mechanism implemented in Cocoa.
Own implementation
Let's say we are developing a completely new game that will certainly blow up the App Store. After passing the next level, we need to do two things:
- Show congratulatory screen.
- Open access to new levels.
It is worth noting that the first and second actions are not related to each other. We can execute them in random order. We also suspect that in the near future it will be necessary to expand the number of such actions (for example, we want to send some data to our server or check if the user has earned an achievement in the Game Center).
We apply the studied pattern of the observer. Let's start with the observer protocol.
@protocol GameStateObserver
- (void) completedLevel: (Level *) level withScore: (NSUInteger) score;
@end
Everything is pretty transparent here: observers will implement the protocol
GameStateObserver
. We will deal with the subject.@protocol GameStateSubject
- (void) addObserver: (id) observer;
- (void) removeObserver: (id) observer;
- (void) notifyObservers;
@end
Let's move on to the classes that interest us. Let the state of the current game be stored in the class object
GameState
. Then its definition will look something like this:@interface GameState: NSObject{
...
NSMutableSet * observerCollection;
}
@property (readonly) Level * level;
@property (readonly) NSUInteger score;
...
- (void) updateState;
@end
At the same time, we believe that the method
updateState
is called every time significant changes occur in the game. Here is a part of the implementation GameState
:@implementation GameState
...
- (void) addObserver: (id) observer {
[observerCollection addObject: observer];
}
- (void) removeObserver: (id) observer {
[observerCollection removeObject: observer];
}
- (void) notifyObservers {
for (idobserver in observerCollection) {
[observer completedLevel: self.level withScore: self.score];
}
}
- (void) updateState {
...
if (levelCompleted) {
[self notifyObservers];
}
}
@end
Now, for any object that needs to know about the successful completion of the level, it is enough to implement the protocol
GameStateObserver
and subscribe to the notification of successful completion. The appropriate code would look something like this:GameState * gameState = [[GameState alloc] init];
[gameState addObserver: levelManager];
[gameState addObserver: levelViewController];
Here
levelViewController
is the controller responsible for the interface of the game process, and levelManager
is the model object corresponding to the levels.Discussion
We examined a rather primitive example, which, however, is ubiquitous. It’s immediately obvious that the solution is quite flexible. It is worth noting that in the notification we decided to transfer some data as parameters. Such an implementation has its pros and cons. It may be convenient to use the following protocol variant
GameStateObserver
:@protocol GameStateObserver
- (void) levelCompleted: (id) subject;
@end
The corresponding option
GameStateSubject
would look like this:@protocol GameStateSubject
- (void) addObserver: (id) observer;
- (void) removeObserver: (id) observer;
- (void) notifyObservers;
@property (readonly) Level * level;
@property (readonly) NSUInteger score;
@end
Observer at Cocoa: Notifications
It turns out that Cocoa has a mechanism to implement an observer pattern. To be completely precise, there are two such mechanisms. At the moment, we will focus on the notification mechanism, and leave the second for the future. Further, we will not go into all the subtleties, but only describe the basic functionality.
Informally, the notification mechanism allows you to do two things: subscribe / unsubscribe from the notification and send the notification to all subscribers. A notification is an instance of a class
NSNotification
. Alerts are specified by their string name
type name NSString
. In addition to the name, the alert also contains the subject object
and additional userInfo
type data NSDictionary
. For subscriptions and delivery of notifications is responsible
NSNotificationCenter
. To access it, in most cases, just call the class method defaultCenter
. To subscribe to a message, the alert center has a method
addObserver:selector:name:object:
. The first parameter is the observer, the second is the selector, which will be called upon notification. It must have a signature - (void)methodName:(NSNotification *)
. The third parameter is the name of the alert, the fourth is the subject. If you transfer as an entity nil
, then the notification will be delivered from an arbitrary sender (provided that the name matches). Using alerts, the subscription code would look like this:GameState * gameState = [[GameState alloc] init];
[[NSNotificationCenter defaultCenter] addObserver: levelManager
selector: @selector (levelCompleted :)
name: @ "LevelCompletedNotification" object: gameState];
[[NSNotificationCenter defaultCenter] addObserver: levelViewController
selector: @selector (levelCompleted :)
name: @ "LevelCompletedNotification" object: gameState];
Approximate type of method
levelCompleted:
- (void) levelCompleted: (NSNotification *) notification {
idsubject = [notification object];
Level * level = subject.level;
NSUInteger score = subject.score;
}
Generally speaking, the protocol
GameStateSubject
can be disposed of and used directly GameState
. To unsubscribe from alerts, you need to call one of the methods
removeObserver:
or removeObserver:name:object:
. Sending an alert is an even simpler process. There are methods
postNotificationName:object:
and postNotificationName:object:userInfo:
. The first sets the value to userInfo
zero nil
. A new implementation of the method notifyObservers
follows.- (void) notifyObservers {
[[NSNotificationCenter defaultCenter]
postNotificationName: @ "LevelCompletedNotification" object: self];
}
Comments
The mechanism for sending alerts can do much more than we described. For example, all of the above code is synchronous. To send alerts asynchronously, you need to use alert queues
NSNotificationQueue
. It's also worth noting that everything described works for both Cocoa and Cocoa Touch. In the future, we will still use some features of the iOS platform.
Instead of an afterword
Let's summarize what we have reviewed and learned. So we:
- studied the design pattern of the observer ;
- used his own implementation in a frequently encountered situation;
- Learned to do the same with Cocoa's notification engine.
- learned that design patterns are not scary, not complicated and not cumbersome;
- realized that the patterns are not taken from the ceiling, but from real life (remember the
pimp ofpromoter Vasya)!
In the second part, we will learn about the Key-Value Observing mechanism, which also implements the observer pattern. There are many more patterns ahead!
Useful sources
- E. Gamma, R. Helm, R. Johnson, J. Vlissides Receptions of object-oriented design. Design Patterns = Design Patterns: Elements of Reusable Object-Oriented Software. - St. Petersburg: Peter, 2007. - S. 366. - ISBN 978-5-469-01136-1 (also ISBN 5-272-00355-1)
- E. Freeman, E. Freeman, C. Sierra, B. Bates Design Patterns = Head First Design Patterns - St. Petersburg: Peter, 2011. - P. 656. - ISBN 978-5-459-00435-9
- Notification Programming Topics - iOS Developer Library