I. KVO (key-value Observing)

KVO is an objective-C implementation of the Observer Pattern. It is the basis of Cocoa Binding. The observer is notified when one of the observed properties changes.

  • Kvo code practices
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    / / / / KVO principle
    Person *p1 = [[Person alloc]init];
    p1.age = 10;
    Person *p2 = [[Person alloc]init];
    // Listen for the age property of the p2 object
    [p2 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

    // View the changes to the object after adding listeners
    [self getMethodName:p1];
    [self getMethodName:p2];

    // Add changes to classes and superclasses after listening
    NSLog(@"p1-Class:%@",[p1 class]);
    NSLog(@"p2-Class:%@",[p2 class]);
    NSLog(@"p1-SupperClass:%@",class_getSuperclass(object_getClass(p1)));
    NSLog(@"p2-SupperClass:%@",class_getSuperclass(object_getClass(p2)));
    

    NSLog(@ "= = = = = = = = = = = = =");
    // Modify the properties of p2 object
    p2.age = 50;
}

- (void)getMethodName:(id)obj
{
    NSLog(@ "= = = = = = = = = = = = = = =");
    unsigned int count = 0;
    Method *ms = class_copyMethodList(object_getClass(obj), &count);
    NSLog(@"Objc:%@",obj);
    NSLog(@"isa:%@", object_getClass(obj));
    for(int i = 0; i<count; ++i){// struct objc_method_description *des = method_getDescription(ms);
        SEL s = method_getName(*ms);
        IMP imp = method_getImplementation(*ms);
        NSLog(@"%s===%p",sel_getName(s),imp);
        ++ms;
    }
    NSLog(@ "= = = = = = = = = = = = = = =");
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey.id> *)change context:(void *)context{
    NSLog(@" Listen to %@ change %@", object, keyPath,change);
}
Copy the code

Output:

Two, KVO implementation principle analysis

KVO is implemented based on the Runtime mechanism. When a property of an observed object is observed for the first time, the system dynamically creates a derived class from the class of the observed object during runtime. In this derived class, the setter method of the observed property in the base class is overridden. The derived class calls the two methods of NSObject, willChangeValueForKey: and didChangevlueForKey:, within the setter method being overridden to implement the real notification mechanism.

Changes to objects before and after adding listeners

Changes to before and after object ISA Pointers

Object p1 and p2 are both instantiated by the Person class, but after adding kVO to object P2, the isa pointer to object P2 points to a new object, NSKVONotifying_Person. This shows that at runtime the system creates a new subclass NSKVONotifying_Person that inherits from the Person class and points the ISA pointer to the p2 object to this new class object.

When the setAge method is called, the isa pointer to p2 finds the new NSKVONotifying_Person and calls the overridden setAge method inside, triggering the listening method

After consulting the data, we can learn.

The setage method in NSKVONotifyin_Person actually calls the Fundation C function _NSsetIntValueAndNotify, and what it does internally is say, First call willChangeValueForKey will change the method, then call the setage method of the parent class to assign a value to a member variable, and finally call didChangeValueForKey to change the method. The listener’s listener method is called in didChangeValueForKey, which ends up in the listener’s observeValueForKeyPath method.

Verify that KVO is implemented as described above

We have already verified that the isa pointer points to a subclass of Person created by Runtime, NSKVONotifyin_Person, when executing the listener. In addition, we can print the address of the method to see how the address of p1 and P2 setage method changes before and after adding KVO.

NSLog(@" before adding KVO listener -p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]); NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [p1 addObserver:self forKeyPath:@"age" options:options context:nil]; NSLog(@" after adding KVO listener -p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);Copy the code

Output:

We found that before KVO listening was added, the address of P1 and P2’s setAge method was the same, but after KVO listening, the address of P1’s setAge method was changed. We used the printing method to realize the change and found that it was exactly the same as what we said above. The implementation of THE setAge method of P1 is transformed by the setAge method of the Person class method into the _NSsetIntValueAndNotify function of the Foundation framework of C language. The Foundation framework calls different methods depending on the type of property. For example, with the age property of type int we defined earlier, we see the _NSsetIntValueAndNotify function called in the Foundation framework. So let’s change the property type of age to double and reprint it

We found that the function called into _NSSetDoubleValueAndNotify, so this suggests that the Foundation framework has a lot of this type of function, through the different types of different function called attributes. So we can assume that there are many other Foundation frameworks such as _NSSetBoolValueAndNotify, _NSSetCharValueAndNotify, _NSSetFloatValueAndNotify, and _NSSetLongValueAn DNotify and so on. You can go to the Foundation framework file and query the keywords on the command line to find relevant functions

NSKVONotifyin_Person Internal structure

NSKVONotifyin_Person implements other methods besides set and GET

  • Class
  • dealloc
  • _isKVOA

How do I trigger KVO manually

Person *p1 = [[Person alloc] init]; P1. The age = 1.0; NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [p1 addObserver:self forKeyPath:@"age" options:options context:nil]; [p1 willChangeValueForKey:@"age"]; [p1 didChangeValueForKey:@"age"]; [p1 removeObserver:self forKeyPath:@"age"];Copy the code

Output:

Through the print we can find that didChangeValueForKey within a method call the observeValueForKeyPath success: ofObject: change: context:, and the value of the age did not change.

  • reference
  • Juejin. Cn/post / 684490…
  • www.jianshu.com/p/82893abc5…