concept

KVO is an Objective-C implementation of the Observer Pattern. The observer object is notified when one of the properties of the observed object changes. Generally, objects that inherit from NSObject support KVO by default. KVO is the poster child for responsive programming.

The basic use

  1. Register observers and monitor
_p = [[Person alloc] init]; /* 1.p: object to be observed 2. observer 3. KeyPath: property to be observed 4. Options: property configuration 5. Context, which is used to distinguish between different KVO listening, registered observers when the context to get the value of the (generally pass nil) * / / / / / options4 enumerated values NSKeyValueObservingOptionNew: / / change after dictionary involves changing the value of the / / NSKeyValueObservingOptionOld: / / change the dictionary include changing the value of the former / / NSKeyValueObservingOptionInitial: / / after registration immediately trigger the KVO notifications / / NSKeyValueObservingOptionPrior: [_p addObserver:self forKeyPath:@"age"] options:NSKeyValueObservingOptionNew context:nil];Copy the code
  1. Implement the observation method
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
}
Copy the code

3. Remove the listener

[_p removeObserver:self forKeyPath:@"age"];
Copy the code

Manual trigger

KVO is usually triggered automatically, calling the observation method when the observed value changes, but sometimes the observation method is called under certain circumstances, which can be done in manual mode.

  1. Overrides in the observed class+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
+ (BOOL) automaticallyNotifiesObserversForKey: (nsstrings *) key {/ / changed to manual mode return NO; }Copy the code
  1. Manual trigger
[self.person willChangeValueForKey:@"age"];
[self.person didChangeValueForKey:@"age"];
Copy the code

Underlying implementation principle

The underlying implementation of KVO is roughly the following steps:

1. Runtime dynamically generates a subclass of Person (also called a derived class). Modify the ISA pointer of the listening object to point to a derived class. 3. Override the property set method of NSKVONotifying_Person to listen for property changes 4. The – (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object is called when the set method is called Change :(nullable NSDictionary

*)change context:(nullable void *) the context method tells the listener that the listening property has changed
,>

The breakpoint shows that the class isa points to has indeed changed

We can also try our own simple implementation of KVO

  1. Add aNSObjectThe classification ofNSObject+KVO.hAnd with aPersonThe classification ofNSKVONotifying_Person.h
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface NSObject (KVO) - (void)lcx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; @end NS_ASSUME_NONNULL_END #import "NSObject+KVO.h" #import <objc/message.h> @implementation NSObject (KVO) - (void)lcx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options Object_setClass (self, NSClassFromString(@"NSKVONotifying_Person")); //self -> p; // Select OBJC_ASSOCIATION_RETAIN, because to prevent observer objects, which is why system KVO must remove observers. Objc_setAssociatedObject (self, @"observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end ---------------------------------------------------------------- #import "Person.h" NS_ASSUME_NONNULL_BEGIN @interface NSKVONotifying_Person : Person @end NS_ASSUME_NONNULL_END #import "NSKVONotifying_Person.h" #import <objc/message.h> @implementation NSKVONotifying_Person - (void)setAge (NSInteger)age {//super: is a flag to execute the superclass method [super setAge:age]; Observer_getassociatedobject (self, @"observer"); observer_getAssociateDobject (self, @"observer"); // Send the observeValueForKeyPath method to the observer, [Observer observeValueForKeyPath:@"age" ofObject:self change:nil context:nil]; } @endCopy the code
  1. Add – (void)lcx_addObserver (NSObject *)observer forKeyPath (NSString *)keyPath to class NSObject+ kvo. h Options: (NSKeyValueObservingOptions) options context: (nullable void *) context method, In this method, change the ISA pointer to the NSKVONotifying_Person class and save the observer.

  2. In class nskvonotifying_person. h, override the set method and get an observeValueForKeyPath method in the set method to notify the listener of the change in the property value.

However, the internal implementation of Apple is certainly more complicated, here is a simple implementation of the call process, in fact, the notification implementation is similar to KVO, but the notification is a one-to-many relationship. There are other things that you can try to do here like register a listener method and pass in a block property, call a block in a set method, implement a block version of KVO, make your code more cohesive.

Continue to update the corrections……