The full name of KVO is KeyValueObserving, which is supported by the NSKeyValueObserving protocol. NSObject inherits this protocol, so all subclasses of NSObject can use this method.

1. Steps for using KVO

1. Register the observer (specify the observer and the observed attributes for the observed)

Create an YMPerson object with an age attribute and add a KVO listener to the age attribute

self.person1 = [[YMPerson alloc] init]; self.person1.age = 10; /* options: There are four values, which are: NSKeyValueObservingOptionOld NSKeyValueObservingOptionNew before the change of value for processing method after the change of value for processing method NSKeyValueObservingOptionInitial to initialize values for processing method, once registered, immediately call again. It usually takes the new value, not the old value. NSKeyValueObservingOptionPrior points 2 times call. Before the value changes and after the value changes. */ / register a listener to listen on the specified key path [self.person addObserver:self forKeyPath:@"age" options:options context:@"context-age"];Copy the code

⚠️ : the value of the context parameter, corresponding to the context in the callback method

Purpose: If you are listening for multiple property changes in a controller, and each property change corresponds to a different event processing, you can use the context parameter to distinguish between observer registration

2. Implement the callback method

// When the listener's property value changes, - (void) observeforkeypath :(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{NSLog(@" monitoring %@ - %@ - %@", object, keyPath, change, context); }Copy the code

3. Modify the value of the property to be monitored and check whether the listening is successful

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person1.age = 11;
}
Copy the code

4. Unregister the observer

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

Two, KVO to realize monitoring process analysis

Question: after registering the observer, modify the value of the observation object attribute, it will go back to the above process KVO is how to achieve?

1. The setAge method modifies the properties

Through OC’s messaging mechanism:

Call self.person1.age = 11; –> [self.person setAge:10] –> objc_msgsend(person,@selector(setAge))

The console prints isa Pointers for Person1 and Person2 respectively to:

From the basic principle of iOS – the nature and classification of objects know: no KVO listening instance object self.person2.isa pointer points to the YMPerson class object; Isa points to the NSKVONotifying_YMPerson class, which isa subclass of YMPerson. The newly created NSKVONotifying_YMPerson inherits from YMPerson and overrides the setAge and class methods

Code simulation implementation process:

@implementation YMPerson

- (void)setAge:(int)age {
    _age = age;
    NSLog(@"setAge:");
}
- (void)willChangeValueForKey:(NSString *)key {
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey");
}
- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"didChangeValueForKey - begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey - end");
}
@end

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.person1.age = 11;
}

Copy the code

Print result:

2, validation,

1) NSKVONotifying_YMPerson

self.person1 = [[YMPerson alloc] init]; self.person1.age = 10; self.person2 = [[YMPerson alloc] init]; self.person2.age = 20; Object_getClass (**self**. Person1),object_getClass(self. Person2)); / / to add KVO person1 objects to monitor / / runtime dynamically create a class NSKVONotifying_YMPerson NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"context-age"]; Object_getClass (self.person1),object_getClass(self.person2));Copy the code

Print result:

2) Inherit from YMPerson and rewrite the class method

Code verification:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { self.person1.age = 11; self.person2.age = 22; NSLog(@" [instance class method] - %@ - %@",[_person1 class],[_person2 class]); NSLog(@" [instance class method] - %@ - %@",[_person1 class],[_person2 class]); // Get the Class object Class personClass = object_getClass(self.person1); Class personClassFather = class_getSuperclass(personClass); NSLog(@" class object: %@ -- parent: %@",personClass, personClassFather); }Copy the code

Print result:

⚠️ Note: the [YMPerson Class] method cannot be used here

Because NSKVONotifying_YMPerson overrides the class method, [YMPerson Class] does not return the original object but the parent of the original object, the YMPerson class. The isa pointer to the YMPerson class should be NSKVONotifying_YMPerson, but Apple didn't want to expose the internal implementation of the NSKVONotifyin_YMPerson class, so it overwrote the class method. The YMPerson class is returned directly, so when we call the Person class method, we return the Person class.Copy the code

3) The setAge method is overwritten

The setAge method in NSKVONotifyin_Person actually calls the C language function _NSSetIntValueAndNotify in Foundation framework. The internal operation of _NSSetIntValueAndNotify is equivalent to calling willChangeValueForKey first, then setAge of the parent class to assign a value to a member variable, and finally didChangeValueForKey. The listener’s listener method is called in didChangeValueForKey, which eventually leads to the listener’s observeValueForKeyPath method.

Code verification:

NSLog(@"person1 adds KVO listener before -% p - %p", [self.person1 methodForSelector: @selector(setAge:)], [self.person2 methodForSelector: @selector(setAge:)]); / / to add KVO person1 objects to monitor / / runtime dynamically create a class NSKVONotifying_YMPerson NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"context-age"]; NSLog(@"person1 adds KVO listener after -% p - %p", [self.person1 methodForSelector: @selector(setAge:)], [self.person2 methodForSelector: @selector(setAge:)]);Copy the code

Print result:

4) Internal structure of NSKVONotifyin_Person

As a subclass of YMPerson, NSKVONotifyin_YMPerson has a superclass pointer to the YMPerson class, and the setAge method inside NSKVONotifyin_YMPerson is implemented separately. Person1, person2, NSKVONotifyin_YMPerson, NSKVONotifyin_YMPerson

- (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor brownColor]; self.person1 = [[YMPerson alloc] init]; self.person1.age = 10; self.person2 = [[YMPerson alloc] init]; self.person2.age = 20; / / to add KVO person1 objects to monitor / / runtime dynamically create a class NSKVONotifying_YMPerson NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"context-age"]; Class personClass1 = object_getClass(**self**.person1); Class personClass2 = object_getClass(**self**.person2); [self printMethodNamesOfClass:personClass1]; [self printMethodNamesOfClass:personClass2]; } - (**void**)printMethodNamesOfClass:(Class)cls { unsigned int count; Method *methodList = class_copyMethodList(CLS, &count); // Store method name NSMutableString *methodNames = [NSMutableString String]; For (int I =0; i<count; Method MTD = methodList[I]; NSString *mtdName = NSStringFromSelector(method_getName(MTD)); [methodNames appendString:mtdName]; [methodNames appendString:@", "]; } // Free (methodList); // Prints the method name NSLog(@"%@: %@", CLS,methodNames); }Copy the code

Print result:

Answer questions and clear up doubts

  • Is the isa pointer to NSKVONotifying_YMPerson the same as the ISA pointer to the class object of YMPerson?

    Answer: Point to the respective metaclass object meta-class

  • NSKVONotifying_YMPerson = NSKVONotifying_YMPerson [general] KVO failed to allocate class pair for name NSKVONotifying_YMPerson, automatic key-value observing will not work for this class

    A: Classes created manually conflict with classes created dynamically by the runtime; You can delete or deselect not to compile

  • NSKVONotifying_Person overrides the setAge method to trigger the callback. A: The setAge method in NSKVONotifyin_YMPerson actually calls the Fundation C function _NSsetIntValueAndNotify, and the internal operation of _NSsetIntValueAndNotify is equivalent to, The willChangeValueForKey method is called first, followed by the setAge method of the parent class to assign a value to a member variable, and finally the didChangeValueForKey method. The listener’s listener method is called in didChangeValueForKey, which eventually leads to the listener’s observeValueForKeyPath method.

  • What is the internal structure of NSKVONotifyin_Person?

    A: NSKVONotifying_YMPerson memory structure and method call order

Four,

1. What is the nature of KVO?

  • Use RuntimeAPI to dynamically generate A subclass NSKVONotifying_A and have the ISA pointer to the instance object of A point to this new subclass
  • When modifying the properties of an instance object, the Foundation framework’s _NSSetXXXCValueAndNotify function is called: 1).willChangevalueForKey 2). Parent’s original setter method 3).didChangevalueForKey 4). Internal will trigger the listener observe surveillance method observeValueForKeyPath: ofObject: change: context:

When we register an observer for an object and add a KVO listener, the system changes the isa pointer to the object. At Runtime, the Runtime dynamically creates A new subclass, NSKVONotifying_A, to override the set method by pointing the ISA pointer to this subclass. The set implementation internally calls the willChangeValueForKey method, the original setter implementation, didChangeValueForKey method, And internal didChangeValueForKey method will be called the listener observeValueForKeyPath: ofObject: change: context: monitoring method.

2. How to trigger KVO manually?

Manually call the willChangeValueForKey and didChangeValueForKey methods.

3. Does directly modifying a member variable trigger KVO?

No trigger. Because the nature of KVO is that you can add willChangeValueForKey and didChangeValueForKey manually when calling setter methods on objects

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self.person1 willChangeValueForKey:@"height"];
    self.person1->_height = 85;
    [self.person1 didChangeValueForKey:@"height"];
}
Copy the code

KVO essential analysis Demo Password: MDKB