I. Overview of KVO

KVO (key-value Observing) is a mechanism that allows you to register as an observer of other objects. When an attribute Value of the object being observed changes, the registered observer can be notified.

2. Basic use of KVO

The following steps must be performed in order for an object to receive a key value for a KVO compatible property notification:

  • To register an observer with the observed object, use this method:addObserver:forKeyPath:options:context:.
  • implementationobserveValueForKeyPath:ofObject:change:context:To receive notification messages about changes in values within the observer.
  • Called before the observer is freed from memoryremoveObserver:forKeyPath:To remove the observer.
  • 1. Register observers
- (void)addObserver:(NSObject *)observer
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(nullable void *)context;
Copy the code
  • 2. Register observers
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context;
Copy the code
  • 3. Unregister the observer
- (void)removeObserver:(NSObject *)observer 
            forKeyPath:(NSString *)keyPath;
Copy the code

1. Basic use

First, we have a Person class with only a name attribute, which looks like this:

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation Person


@end
Copy the code

ViewController. M:

@interface ViewController () @property (nonatomic, strong) Person *person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _person = [Person alloc]; / / register the self, that is, the controller for his observers [_person addObserver: self forKeyPath: @ "name" options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } - (void)touchesBegan:(NSSet< touch *> *)touches withEvent:(UIEvent *)event{_person.name = @@view; } // response method - (void) observeforkeypath :(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%@ - %@ - %@",keyPath,object,change); } // removeObserver - (void)dealloc{[_person removeObserver:self forKeyPath:@"name"]; } @endCopy the code

Click on the screen and console output:

2021-01-18 15:59:15.571749+0800 KVO[3322:245856] name - <Person: 0x6000008c0350> - {kind = 1; new = "\U54c8\U54c8"; old = "<null>"; }Copy the code

NSKeyValueChange value:

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {NSKeyValueChangeSetting = 1, / / a value NSKeyValueChangeInsertion = 2, / / insert NSKeyValueChangeRemoval = 3, / / removed NSKeyValueChangeReplacement = 4, / / replace};Copy the code

2. Manually trigger the Observer callback

When we change the value of name, the Observer callback is automatically triggered, but sometimes we don’t always want to notify each time, such as when a condition is met. Let’s start by adding a method in Person to turn off automatic triggering:

/ / for all of them are closed / / + (BOOL) automaticallyNotifiesObserversOfName {/ / return NO; / / / /} can be closed + (BOOL) to a specified key automaticallyNotifiesObserversForKey: (nsstrings *) key {if ([key isEqualToString: @ "name"]) { NSLog(@" auto trigger off "); return NO; } return YES; }Copy the code

Add manually triggered code to the person.name assignment:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{// the value of name is about to change [_person willChangeValueForKey:@"name"]; _person.name = @person. name; [_person didChangeValueForKey:@"name"]; }Copy the code

3. Observe attributes. Attribute change

The Person class has the Dog attribute, and the Dog class has the age attribute. Observe the change of age as follows:

[_person addObserver:self forKeyPath:@"dog.age" options:(NSKeyValueObservingOptionNew) context:nil];
Copy the code

4. Observe multiple attribute changes

Add firstName and lastName to the Person class. Add a listener to name and receive notification when either firstName or lastName changes:

  • inPersonAdd class methods to a class+ keyPathsForValuesAffectingValueForKey, returns a container.
  • Registered to observePersonnameProperty, so that you canfirstNamelastNameWhen changes, the corresponding callback is also received.
+ (NSSet<NSString *> *)keyPathsForValuesAffectingName {
    NSSet *keyPaths = [NSSet setWithArray:@[@"firstName",@"lastName"]];
    return keyPaths;
}
Copy the code

4. Observe mutable array changes

Person adds a mutable array property. When adding data to this mutable array, setter methods are not called and KVO callbacks are not triggered, as follows:

- (void)viewDidLoad { [super viewDidLoad]; _person = [Person alloc]; // sons is a mutable array _person.sons = [@[] mutableCopy]; / / then observe the changes of sons [_person addObserver: self forKeyPath: @ "sons" options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {// scenes created [_person.sons] addObject:@"lili"]; }Copy the code

[_person.sons addObject:@”lili”]; The KVO callback cannot be triggered. For the collection type of a mutable array, the KVO callback must be triggered by adding elements to the mutable array using the mutableArrayValueForKey method.

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [[_person mutableArrayValueForKey:@"sons"]  addObject:@"lili"]; }Copy the code

Third, KVO low-level exploration

KVO uses isa-Swizzling technology for automatic key observation.

  • The ISA pointer, which points to the class of the object, holds a schedule. The dispatch table essentially contains Pointers to the methods implemented by the class, as well as other data.
  • When an observer is registered for an object’s properties, the ISA pointer to the observed object is modified to point to the intermediate class instead of the real class. As a result, the value of the ISA pointer does not necessarily reflect the actual class of the instance.
  • You should never rely on isa Pointers to determine class members. Instead, you should use this[ class]Method to determine the class of an object instance.

1. Verify that KVO only observes attributes

  1. Create a Project at random, create a class that has one property and one member variable.
  2. Instantiate an object of a class and add attributes and member variables to the observerViewController
  3. addtouchBeginMethod to change both properties and instance variables by clicking on the screen.
  4. KVOTo observe is to havesetterMethod variables, which can be member variables, but must be usedKVCProperties can be directly assigned for observation.

(Note: specific verification is self-implemented)

2. Verify the KVO intermediate class

According to the official documentation, after registering a KVO observer, the isa pointer to the observed object changes to point to an intermediate class.

Comparison of class names before and after registration:

Get the parent of the NSKVONotifying_Person classPerson.NSKVONotifying_PersonPersonThe subclass:

We can also get the class information with the following code:

- (void)printClassAllMethod:(Class)cls{ unsigned int count = 0; Method *methodList = class_copyMethodList(cls, &count); for (int i = 0; i<count; i++) { Method method = methodList[I]; SEL sel = method_getName(method); IMP imp = class_getMethodImplementation(cls, sel); NSLog(@"%@-%p",NSStringFromSelector(sel),imp); } free(methodList); } ///// call [self printClassAllMethod:objc_getClass("NSKVONotifying_Person")];Copy the code

To:

2021-01-18 20:47:41.791402+0800 KVO[4396:407755] setNickName: -0x10918C54b 2021-01-18 20:47:41.791516+0800 KVO[4396:407755] class-0x10918AFD5 2021-01-18 20:47:41.791612+0800 KVO[4396:407755] dealloc-0x10918AD3a 2021-01-18 20:47:41. 791706 + 0800 KVO _isKVOA x10918ad32 0 [4396-407755]Copy the code

The NSKVONotifying_Person class has four methods:

  • setXxx: overrides the property being observedsetMethods.
  • class: Rewrite your ownclassMethods.
  • dealloc: Rewrite your owndeallocMethods.
  • _isKVOA: Judge whether or notKVOThe generated intermediate class.

NSKVONotifying_xxx is the object whose ISA points to the class NSKVONotifying_xxx after the KVO observation. The set method of NSKVONotifying_xxx is used to change the property.

The middle subclass NSKVONotifying_xxx overrides the setter, dealloc, class methods of the parent class. In addition, when the observer logs out, the intermediate class will not be destroyed, but cached, when necessary to call directly, reducing the cost of adding observers next time.

Reference documentation

  1. Key-value Observing Programming Guide This document is an official Apple document

  2. [iOS] KVO Basic Principles code_ce