📜 ⬆️ ⬇️

Design patterns for iOS developers. Observer Part I

Instead of the preface


It has been 17 years since the legendary Gang of Four book on Design Patterns was published. Despite such a solid period, it is hard to challenge the relevance of the techniques described in it. Design patterns live and develop. They are used, discussed, scolded and praised. Unfortunately, for many they still remain an unnecessary abstraction.

When discussing various issues of programming with colleagues both in life and on various resources, quite often you have to explain the importance of one or another pattern. So the idea was born with specific examples to show how their use can make a programmer's life easier. Even if we are talking about 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. When he started looking for a job, Vasya realized that he knew almost nothing and could not. Although, wait ... Vasya has so many beautiful girls who know him, and don’t feed him with bread - let him enter the cool party without an invitation. Vasya is also known in all relevant institutions of his city. Finally, Vasya understands: for the party to succeed (wealthy visitors spend a lot of money), it would be nice to fill it with beautiful girls (on which this money will be spent).
')
So Vasya became a pimp opened his business. The owners of the establishments turn to Vasya (who does not know Vasya ?!) with news about fashionable private events. Vasya informs his girlfriend that he can take them there. Where does he take the girls? It's simple: take the Light. She met Vasya at the club last week. Sveta had long dreamed of going to some elite get-together, so she asked Vasya to write down her phone number. She is a pretty girl, dressed very well, so Vasya agreed. So Sveta signed up for Vasiny services and became an observer . However, Vasya warned her that he would call when the time came.

A year has passed, Sveta graduated from the institute, found a job and, most importantly, married! She no longer wants to go to parties, so yesterday she called Vasya and asked not to pester her with her idiotic messages anymore. So she unsubscribed from Vasya's (very suspicious) subject . However, she knows that she can always sign again (she is still pretty, young, and dresses well).

Meanwhile, Vasya did not stand still, but expanded his business. He recently met some 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). Expansion of business went unnoticed: really, what's the difference, who to call? Both girls and football players have telephones. Sometimes, when reporting a new event, Vasya even confuses with whom he is talking.

Shorter example

Those who closely followed Vasin’s business could immediately recall the newsletters. Indeed, we can leave your email address on your favorite resource and receive spam important and interesting news without having to visit the corresponding web page every day. As in the case of Sveta, we always (in fact, if we are lucky) can unsubscribe from the newsletter.

Definition

Gang of four



Comments

So, the observer pattern defines one-to-many dependency. In this case, an object that reports its changes is called a subject , and those objects to which it reports them are observers .

It is worth noting an important feature of the observer pattern: the subject may be unaware of the observers. So, Vasya did not see any difference between girls and football players.

Structure

image

Supervisor for iOS developer


Let's move on to programming. First, we will examine our own implementation of the observer pattern using a specific example, and then we will analyze the alert mechanism implemented in Cocoa.

Own implementation

Suppose 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:
  1. Show the congratulatory screen.
  2. Open access to new levels.

It is worth noting that the first and second actions are not related to each other. We can perform them in any order. We also suspect that we will soon need to expand the number of such actions (for example, we will want to send some data to our server or check whether the user has earned an achievement in the Game Center).

Apply the observed observer pattern. Let's start with the observer protocol.

@protocol GameStateObserver <NSObject>

- ( void ) completedLevel : ( Level * ) level withScore : ( NSUInteger ) score;

@end


Everything is pretty transparent here: observers will implement the GameStateObserver protocol. We will deal with the subject.

@protocol GameStateSubject <NSObject>

- ( void ) addObserver : ( id <GameStateObserver> ) observer;
- ( void ) removeObserver : ( id <GameStateObserver> ) observer;
- ( void ) notifyObservers;

@end


Let us turn to the classes that interest us. Let the state of the current game be stored in an object of the GameState class. Then its definition will look something like this:

@interface GameState : NSObject <GameStateSubject> {
...
NSMutableSet * observerCollection;
}

@property ( readonly ) Level * level;
@property ( readonly ) NSUInteger score;

...
- ( void ) updateState;

@end


In this case, we believe that the updateState method updateState invoked every time, as in the game there are significant changes. We present a part of the implementation of GameState :

@implementation Game State

...
- ( void ) addObserver : ( id <GameStateObserver> ) observer {
[ observerCollection addObject : observer ] ;
}

- ( void ) removeObserver : ( id <GameStateObserver> ) observer {
[ observerCollection removeObject : observer ] ;
}

- ( void ) notifyObservers {
for ( id <GameStateObserver> observer in observerCollection ) {
[ observer completedLevel : self.level withScore : self.score ] ;
}
}

- ( void ) updateState {
...
if ( levelCompleted ) {
[ self notifyObservers ] ;
}
}

@end


Now, any object that needs to know about a successful level passing is sufficient to implement the GameStateObserver protocol and subscribe to a success alert. The corresponding code will look something like this:

GameState * gameState = [ [ GameState alloc ] init ] ;
[ gameState addObserver : levelManager ] ;
[ gameState addObserver : levelViewController ] ;


Here, levelViewController is the controller responsible for the gameplay interface, and levelManager is the model object that responds to the levels.

Discussion

We looked at a rather primitive example, which, however, is ubiquitous. Immediately it is clear that the solution is quite flexible. It is worth noting that in the notification we decided to pass some data as parameters. This implementation has its advantages and disadvantages. It may be convenient to use the following version of the GameStateObserver protocol:

@protocol GameStateObserver <NSObject>

- ( void ) levelCompleted : ( id <GameStateSubject> ) subject;

@end


The corresponding GameStateSubject would look like this:

@protocol GameStateSubject <NSObject>

- ( void ) addObserver : ( id <GameStateObserver> ) observer;
- ( void ) removeObserver : ( id <GameStateObserver> ) observer;
- ( void ) notifyObservers;

@property ( readonly ) Level * level;
@property ( readonly ) NSUInteger score;

@end


Cocoa Observer: Notifications

It turns out that Cocoa has a mechanism to implement the observer pattern. To be exact, there are two such mechanisms. At the moment we will focus on the mechanism of alerts, and leave the second for the future. Further, we will not go into all the details, but only describe the basic functionality.

Informally, the notification mechanism allows you to do two things: subscribe / unsubscribe from the alert and send an alert to all subscribers. The alert is an instance of the NSNotification class. Alerts are set by their NSString string name. In addition to the name, the alert also contains the subject object and additional userInfo data.

NSNotificationCenter is responsible for subscribing and delivering alerts. To access it, in most cases it is enough to 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 nil transmitted as a subject, 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 view of the levelCompleted: method levelCompleted:

- ( void ) levelCompleted : ( NSNotification * ) notification {
id <GameStateSubject> subject = [ notification object ] ;
Level * level = subject.level;
NSUInteger score = subject.score;
}


Generally speaking, the GameStateSubject protocol can be disposed of and used directly by the GameState .

To unsubscribe from alerts, you must call one of the removeObserver: methods or removeObserver:name:object:

Sending an alert is an even simpler process. There are postNotificationName:object: and postNotificationName:object:userInfo: . The first sets the userInfo value userInfo null nil . A new implementation of the notifyObservers method follows.

- ( void ) notifyObservers {
[ [ NSNotificationCenter defaultCenter ]
postNotificationName : @ "LevelCompletedNotification" object : self ] ;
}


Comments

The mechanism for sending alerts is much more able than we have described. For example, all given code is synchronous. To send alerts asynchronously, you must use the NSNotificationQueue alert NSNotificationQueue .

It is 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 epilogue


Let's summarize some of what we have reviewed and learned. So we:

In the second part, we will learn about the Key-Value Observing mechanism, which also implements the observer pattern. There are still many patterns ahead!

Useful sources


Source: https://habr.com/ru/post/126968/


All Articles