📜 ⬆️ ⬇️

Design patterns, iOS view developer. Part 2. The Observer


Content:


Part 0. Singleton Single
Part 1. Strategy
Part 2. The Observer


Today we will deal with the "filling" of the "Observer" pattern. I’ll just say that you don’t have the urgent need to implement this pattern in the iOS world, since the SDK already has NotificationCenter . But for educational purposes, we completely analyze the anatomy and application of this pattern. In addition, self-implementation may be more flexible and, in some cases, more useful.


"It seems to be going to rain" (s)


The authors of the book "Design Patterns" (Eric and Elizabeth Freeman), as an example, suggest applying the "Observer" pattern to the development of the Weather Station application. Imagine what we have: a weather station, and a WeatherData object that processes data from its sensors and transmits it to us. The application consists of three screens: the current weather status screen, the statistics screen and the forecast screen.


We know that WeatherData provides us with this interface:


 // Objective-C - (double)getTemperature; - (double)getHumidity; - (double)getPressure; - (void)measurementsChanged; 

 // Swift func getTemperature() -> Double func getHumidity() -> Double func getPressure() -> Double func measurementsChanged() 

Also, the WeatherData developers WeatherData reported that the measurementsChanged method will be called up with each update of the weather sensors.


Of course, the simplest solution is to write the code directly in this method:


 // Objective-C - (void)measurementsChanged { double temp = [self getTemperature]; double humidity = [self getHumidity]; double pressure = [self getPressure]; [currentConditionsDisplay updateWithTemp:temp humidity:humidity andPressure:pressure]; [statisticsDisplay updateWithTemp:temp humidity:humidity andPressure:pressure]; [forecastDisplay updateWithTemp:temp humidity:humidity andPressure:pressure]; } 

 // Swift func measurementsChanged() { let temp = self.getTemperature() let humidity = self.getHumidity() let pressure = self.getPressure() currentConditionsDisplay.update(with: temp, humidity: humidity, and: pressure) statisticsDisplay.update(with: temp, humidity: humidity, and: pressure) forecastDisplay.update(with: temp, humidity: humidity, and: pressure) } 

This approach is of course bad because:



Therefore, the “Observer” pattern will be very useful in this situation. Let's talk a little about the characteristics of this pattern.


"Observer". What is under the hood?


The main characteristics of this pattern are the presence of a SUBJECT and, in fact, OBSERVERS. Communication, as you have already guessed, is one to many, and when the state of the SUBJECT changes, its OBSERVERS are alerted. At first glance, everything is simple.


The first thing we need is interfaces (protocols) for observers and a subject:


 // Objective-C @protocol Observer <NSObject> - (void)updateWithTemperature:(double)temperature humidity:(double)humidity andPressure:(double)pressure; @end @protocol Subject <NSObject> - (void)registerObserver:(id<Observer>)observer; - (void)removeObserver:(id<Observer>)observer; - (void)notifyObservers; @end 

 // Swift protocol Observer: class { func update(with temperature: Double, humidity: Double, and pressure: Double) } protocol Subject: class { func register(observer: Observer) func remove(observer: Observer) func notifyObservers() } 

Now you need to put in order WeatherData (sign on the WeatherData protocol and not only):


 // Objective-C //   WeatherData.h @interface WeatherData : NSObject <Subject> - (void)measurementsChanged; - (void)setMeasurementWithTemperature:(double)temperature humidity:(double)humidity andPressure:(double)pressure; // test method @end //   WeatherData.m @interface WeatherData() @property (strong, nonatomic) NSMutableArray<Observer> *observers; @property (assign, nonatomic) double temperature; @property (assign, nonatomic) double humidity; @property (assign, nonatomic) double pressure; @end @implementation WeatherData - (instancetype)init { self = [super init]; if (self) { self.observers = [[NSMutableArray<Observer> alloc] init]; } return self; } - (void)registerObserver:(id<Observer>)observer { [self.observers addObject:observer]; } - (void)removeObserver:(id<Observer>)observer { [self.observers removeObject:observer]; } - (void)notifyObservers { for (id<Observer> observer in self.observers) { [observer updateWithTemperature:self.temperature humidity:self.humidity andPressure:self.pressure]; } } - (void)measurementsChanged { [self notifyObservers]; } - (void)setMeasurementWithTemperature:(double)temperature humidity:(double)humidity andPressure:(double)pressure { self.temperature = temperature; self.humidity = humidity; self.pressure = pressure; [self measurementsChanged]; } @end 

 // Swift class WeatherData: Subject { private var observers: [Observer] private var temperature: Double! private var humidity: Double! private var pressure: Double! init() { self.observers = [Observer]() } func register(observer: Observer) { self.observers.append(observer) } func remove(observer: Observer) { self.observers = self.observers.filter { $0 !== observer } } func notifyObservers() { for observer in self.observers { observer.update(with: self.temperature, humidity: self.humidity, and: self.pressure) } } func measurementsChanged() { self.notifyObservers() } func setMeasurement(with temperature: Double, humidity: Double, and pressure: Double) { // test method self.temperature = temperature self.humidity = humidity self.pressure = pressure self.measurementsChanged() } } 

We added the test method setMeasurement to simulate changes in sensor states.


Since the register and remove methods will rarely change from subject to subject, it would be good to have their default implementation. In Objective-C, for this we need an extra class. But first, let's rename our protocol and remove these methods from it:


 // Objective-C @protocol SubjectProtocol <NSObject> - (void)notifyObservers; @end 

Now add the Subject class:


 // Objective-C //   Subject.h @interface Subject : NSObject @property (strong, nonatomic) NSMutableArray<Observer> *observers; - (void)registerObserver:(id<Observer>)observer; - (void)removeObserver:(id<Observer>)observer; @end //   Subject.m @implementation Subject - (void)registerObserver:(id<Observer>)observer { [self.observers addObject:observer]; } - (void)removeObserver:(id<Observer>)observer { [self.observers removeObject:observer]; } @end 

As you can see, in this class there are two methods and an array of our observers. Now, in the WeatherData class WeatherData we remove this array from the properties and inherit from Subject , and not from NSObject :


 // Objective-C @interface WeatherData : Subject <SubjectProtocol> 

In the swift, thanks to the extensions of the protocols, an additional class is not needed.
We simply include the observers property in the Subject protocol:


 // Swift protocol Subject: class { var observers: [Observer] { get set } func register(observer: Observer) func remove(observer: Observer) func notifyObservers() } 

And in the protocol extension, we write the default implementation of the register and remove methods:


 // Swift extension Subject { func register(observer: Observer) { self.observers.append(observer) } func remove(observer: Observer) { self.observers = self.observers.filter {$0 !== observer } } } 

Receive signals


Now we need to implement the screens of our application. We implement only one of them: CurrentConditionsDisplay . The implementation of the rest is similar.


So, we create the class CurrentConditionsDisplay , add two properties to it and the display method (this screen should show the current weather condition, as we remember):


 // Objective-C @interface CurrentConditionsDisplay() @property (assign, nonatomic) double temperature; @property (assign, nonatomic) double humidity; @end @implementation CurrentConditionsDisplay - (void)display { NSLog(@"Current conditions: %f degrees and %f humidity", self.temperature, self.humidity); } @end 

 // Swift private var temperature: Double! private var humidity: Double! func display() { print("Current conditions: \(self.temperature) degrees and \(self.humidity) humidity") } 

Now we need to "sign" this class on the Observer protocol and implement the necessary method:


 // Objective-C //    CurrentConditionsDisplay.h @interface CurrentConditionsDisplay : NSObject <Observer> //    CurrentConditionsDisplay.m - (void)updateWithTemperature:(double)temperature humidity:(double)humidity andPressure:(double)pressure { self.temperature = temperature; self.humidity = humidity; [self display]; } 

 // Swift class CurrentConditionsDisplay: Observer { func update(with temperature: Double, humidity: Double, and pressure: Double) { self.temperature = temperature self.humidity = humidity self.display() } 

Almost done. It remains to register our observer with the subject (also do not forget to delete the registration during deinitialization).


For this we need another property:


 // Objective-C @property (weak, nonatomic) Subject<SubjectProtocol> *weatherData; 

 // Swift private weak var weatherData: Subject? 

And initializer with deinitializator:


 // Objective-C - (instancetype)initWithSubject:(Subject<SubjectProtocol> *)subject { self = [super init]; if (self) { self.weatherData = subject; [self.weatherData registerObserver:self]; } return self; } - (void)dealloc { [self.weatherData removeObserver:self]; } 

 // Swift init(with subject: Subject) { self.weatherData = subject self.weatherData?.register(observer: self) } deinit { self.weatherData?.remove(observer: self) } 

Conclusion


We wrote a fairly simple implementation of the "Observer" pattern. Our option, of course, not without flaws. For example, if we add a fourth sensor, we will need to rewrite the interface of the observers and the implementation of this interface (to deliver the fourth parameter to the observers), but this is not good. In NotificationCenter , which I mentioned at the very beginning of the article, this problem does not exist. The fact is that there the data transfer takes place in one single parameter-dictionary.


Thank you for your attention, learn and teach others.
After all, while we are learning, we remain young. :)


')

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


All Articles