Good afternoon, harabrachiteli. I hasten to share with you the experience I recently received.
Why is there a need?
As you probably know, the creation of more or less intelligible and serious applications cannot do without competent design. One of the main tasks of modern programming is control over complexity, the requirements for creating flexible and extensible, changeable applications. From this, the concepts of orthogonal programming, maximally reducing connectivity between classes, using the most appropriate architectural solutions (highly competent approaches to creating the project architecture, approaches to designing classes) follow. For many man hours and people’s world experience of all developers, the most natural and successful approaches, called design patterns, were developed ... And approaches to class design can vary to some extent, depending on the programming language used and the desired properties of the object. The pattern described by me today is one of my favorite (and generally quite significant), but meet: ... "
Observer " (in Russian - Observer). Based on the last two sentences, the title of this article follows.
The most complete and detailed description of the Observer pattern can be found in the well-known book “Gangs of Four” -
“Object-oriented design techniques. Design Patterns »
There is also a good
cheat sheet on patterns.
')
All patterns are divided into 3 types
- Behavioral
- generating
- Structural
Observer is a behavioral pattern.
The classic implementation looks like this, but as usual, some deviations from the standard implementation are possible.
What is this "Observer", the available technology
The observer allows you to reduce the number of dependencies in the project, reduce connectivity, increase the independence of objects from each other (reduce knowledge of one object about another, the principle of encapsulation), and proposes an approach to solving a certain group of problems. Regarding my current project, I had the following problem:
There was a view controller for creating a new order (NewOrderViewController) in the Navigation Controller's hierarchy, and there were transitions from it to other views (for choosing a tariff, for choosing a carrier, for choosing a route, choosing an order date and choosing additional services). Earlier, I recalculated the order price for viewWillAppear to NewOrderViewController, but this was not the best solution, because it was necessary to send a network request, and the user could see the waiting indicator for some time (for example). In general, it would be more logical to recalculate the price of the order after changing one of the previously mentioned order parameters. It would be possible to use delegation (or to keep weak references to NewOrderViewController), and call the price recalculation method in appropriate places. But this approach is fraught with complication and some inconvenience. A more appropriate method was chosen - to create an observer who will track model changes, call the recalculation method in the PriceCalculator class, which in turn informed NewOrderViewController about the price / moment calculation start using the delegation.
Now we need to talk about how to construct an observer. This abstraction should be as easy to use as possible, as natural and logical as possible.
First of all, we need to either independently implement one of the observation technologies, or use any one already available.
- (if manually) It is possible to construct such a technology by creating a separate flow of execution and run-loop (cycle) with the detection of changes of the corresponding objects, which we plan to monitor
- (if you use something ready) There are only 2 solutions in standard frameworks for iOS that can satisfy the solution of a similar problem
a) NSNotificationCenter (use of the notification mechanism)
b) KVO (Key-value observing) (observation of changes in class properties)
The approach with NSNotification is a significant drawback - for this, the setters of the required properties would have to be overloaded, and creating NSNotification with the help of
- postNotification:
- postNotification:
and in some places and explicitly state
The most significant plus of KVO is the minimal impact on the observed class, also the possibilities of configuring observability (observing options), relative simplicity.
There is also a rather significant drawback - a serious consumption of productivity (in the case of universal use), but in my case I decided to reconcile
Thus, the choice fell on KVO
Key-value Observing
Some useful articles about KVO:
Official documentation (English), the most complete
two in english
three habrovskaya
To use KVO, you must also understand the basic principles of Key-value coding (key-value coding)
KVO provides observer addition and exclusion methods.
- (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; - (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath; - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context;
And the main method for registering change on observable properties
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
The advantages are also the ability to choose NSKeyValueObservingOptions
- NSKeyValueObservingOptionNew - gets a new value in NSDictionary (called when the value changes)
- NSKeyValueObservingOptionOld - gets the old value in NSDictionary (before the change)
- NSKeyValueObservingOptionInitial - the processing method also works immediately after the appointment of an observer
- NSKeyValueObservingOptionPrior - the handler is triggered twice (both before and after the changes) (not sure)
The options are additive, you can choose several at once using bitwise or
Another plus is the ability to track the property not only of the current object, but of the nested ones (after all, keyPath)
Current implementation
Unfortunately, I had to rub the code listings!
Initially, the idea was to create a base class that implements an observer, but it was decided that this was not profitable. Therefore, all observers are trivially inherited from NSObjects. Since the observer must implement the one-to-many relationship, a mechanism of subscribers was invented. Each class that needs to be notified of any changes - subscribes to the observer and implements the appropriate method from the protocol.
Each subscriber must support the protocol (for AddressPathObserver, this is
, OrderObserver - , :
/ , -
- ! , - , (- , - ), . , NSSet, . - . - , , - , , . , , , . - NSMapTable NSHashTable, . NSHashTable - NSSet, (weak) .
- , ( ) . ( - ). , , . , - , , . , :
KVO - - . , - .
/
. , - . , .
-, (, )
, . - ( ), , . , , . ;)
! , . , / - , , :
[self willChangeValueForKey:@"addressPath"]; [_addressPath addObject:newAddressPoint]; [self didChangeValueForKey:@"addressPath"];
, , ))
, OrderObserver - , :
/ , -
- ! , - , (- , - ), . , NSSet, . - . - , , - , , . , , , . - NSMapTable NSHashTable, . NSHashTable - NSSet, (weak) .
- , ( ) . ( - ). , , . , - , , . , :
KVO - - . , - .
/
. , - . , .
-, (, )
, . - ( ), , . , , . ;)
! , . , / - , , :
[self willChangeValueForKey:@"addressPath"]; [_addressPath addObject:newAddressPoint]; [self didChangeValueForKey:@"addressPath"];
, , ))