KVO underlying principles

As mentioned in the previous article, KVO listener member variables cannot receive callbacks. Let’s verify that this is true.

1. Create a Person class with a public member variable age and a property variable name.

@interface Person : NSObject{
    @public
    int age;
}
@property(strong,nonatomic)NSString * name;
Copy the code

The difference between a member variable and a property: a property automatically generates setter and getter methods, but a member variable does not.

2. Listen for two variables separately in ViewController.

    self.p = [[Person alloc]init];
        
    [self.p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
    [self.p addObserver:self forKeyPath:@"age" options:(NSKeyValueObservingOptionNew) context:NULL];
Copy the code

3. Implement listening callback, log out the changed content and the observed.

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

4. Assign values to the two variables to trigger KVO.

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.p.name = @"name";
    self.p->age = 10;
}
Copy the code

Dot syntax is different from -> :. Dot syntax calls setter methods, while -> accesses member variables directly

5. Make it a habit to remove observers

-(void)dealloc{
    [self.p removeObserver:self forKeyPath:@"name"];
    [self.p removeObserver:self forKeyPath:@"age"];
}
Copy the code

6. After KVO is triggered, the discovery log contains only one content

2019-03-07 21:53:13.105997+0800 KVO[84542:11106254] {kind = 1; new = name; }---<Person: 0x6000007a6600>Copy the code

7. Manually write setters for age in @implementation

-(void)setAge:(int)newAge{
    age=newAge;
}
Copy the code

8. Modify the Age assignment method in the touchesBegan method

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.p.name = @"name";
    [self.p setAge:10];
}
Copy the code

9. Run the program again, trigger KVO, and observe the log information.

2019-03-07 22:24:24.903100+0800 KVO[85477:11167107] {kind = 1; new = name; }-- <Person: 0x600002680a40> 2019-03-07 22:24:24.903477+0800 KVO[85477:11167107] {kind = 1; new = 10; }---<Person: 0x600002680a40>Copy the code

Thus, KVO observations on an object that implements setter methods will only initiate callbacks.

What happens after the observer is added to the object

  • What happens when an observer is added

Print the object name before and after adding the observer

self.p = [[Person alloc]init];
NSLog(@"%s",object_getClassName(self.p));
[self.p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
NSLog(@"------ splitter ----------");
NSLog(@"%s",object_getClassName(self.p));
Copy the code

The printed result is:

Person [88913:11408301] cialis Person [88913:11408301] cialis Person [88913:11408301] ------ ---------- 2019-03-08 10:13:13.102021+0800 KVO[88913:11408301] NSKVONotifying_PersonCopy the code

As you can see from the print, self.p was pointing to Person, and when you add an observer it becomes NSKVONotifying_Person. KVO automatically creates an NSKVONotifying_

after we add an observer to an object.

  • How is the newly generated class related to the original class?

There is a method for printing classes and subclasses

-(void)printClasses:(Class) CLS {// total registered Classes int count = objc_getClassList(NULL, 0); // Create an array containing the given object NSMutableArray *mArray = [NSMutableArray arrayWithObject: CLS]; Class *classes = (Class *)malloc(sizeof(Class)*count); objc_getClassList(classes, count);for(int i = 0; i<count; I ++) {//classes[I] has a parent equal to CLSif(cls == class_getSuperclass(classes[i])) { [mArray addObject:classes[i]]; }} // free classes free(classes); NSLog(@"% @",mArray);
}
Copy the code

Before adding an observer, try printing [Person Class]

[self printClasses:[Person class]];
Copy the code

The printed result is:

[Person, Student](Person, Student)Copy the code

Then add an observer and print again

[self.p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
[self printClasses:[Person class]];
Copy the code

The printed result is:

2019-03-07 22:52:02.555585+0800 KVO[86213:11217129] (Person,"NSKVONotifying_Person",
    Student
)
Copy the code

So KVO automatically creates the NSKVONotifying_

class that inherits from

.

  • What does the newly generated class do?

Here is a method that prints all the methods executed by the current class

-(void)printClassAllMethod:(Class)cls{ unsigned int count = 0; Method *methods = class_copyMethodList(CLS, &count);for(int i = 0 ; i<count; i++) { Method method = methods[i]; MethodSEL = method_getName(method); IMP methodIMP = class_getMethodImplementation(cls, methodSEL); // Print SEL name and IMP address NSLog(@"%@---%p",NSStringFromSelector(methodSEL),methodIMP); } // free(methods); }Copy the code

If SEL quantity changes before and after observation, it means that a new method has been added to it (why not deleted? Subclasses cannot manipulate superclass methods. If the IMP address changes, it represents a rewrite of the IMP. Create a Student class that inherits the implementation method from Person -(void)say; Student to rewrite the – (void) say; And the implementation method -(void)study; -(void)printClassAllMethod:(class) CLS

[self printClassAllMethod:[Person class]];
NSLog(@"------ splitter ----------");
[self printClassAllMethod:[Student class]];
Copy the code

Print result:

2019-03-08 11:35:52.257273+0800 KVO[90824:11859981] say-- 0x109dfD260 2019-03-08 11:35:52.257500+0800 KVO[90824:11859981] ------ ---------- 2019-03-08 11:35:52.257659+0800 KVO[90824:11859981] say 0x109dfd1C0 2019-03-08 11:35:52.257774+0800 KVO[90824:11859981] study-- 0x109dfD1f0 2019-03-08 11:35:52.257976+0800 KVO[90824:11859981] study-- 0x109DFD1f0 2019-03-08 11:35:52.257976+0800 KVO[90824:11859981] class---0x109dfd220Copy the code

In which, the SAY method is overwritten, the IMP address is changed, and the new study method and the overwritten class method are printed.

Print [Person class] and [NSKVONotifying_Person class] to compare what methods the original and new classes execute.

[self printClassAllMethod:[Person class]];
[self.p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
NSLog(@"------ splitter ----------");
[self printClassAllMethod:NSClassFromString(@"NSKVONotifying_Person")];
Copy the code

Print result:

[89658:11656677]. Cxx_destruct -- 0x10b19c1B0 2019-03-08 10:47:01.419936+0800 KVO[89658:11656677] name-- 0x10B19C150 2019-03-08 10:47:01.420134+0800 KVO[89658:11656677]setName: x10b19c170 10:47:01 2019-03-08-0. 421109 + 0800 KVO (89658-11656677) -- -- -- -- -- - line -- -- -- -- -- -- -- -- -- -- 2019-03-08 10:47:01. 421352 + 0800 KVO (89658-11656677)setName:---0x10b4f663a
2019-03-08 10:47:01.421516+0800 KVO[89658:11656677] class---0x10b4f506e
2019-03-08 10:47:01.421649+0800 KVO[89658:11656677] dealloc---0x10b4f4e12
2019-03-08 10:47:01.421782+0800 KVO[89658:11656677] _isKVOA---0x10b4f4e0a
Copy the code

So NSKVONotifying_Person overwrites the setter, class, dealloc and adds _isKVOA.

conclusion

  • Only objects are implementedsetterMethod,KVOIt is to be observed that a callback is initiated.
  • After the observer is added to the object,KVOOne is automatically createdNSKVONotifying_<ClassName>.
  • Automatically createdNSKVONotifying_<ClassName>Class inherits from<ClassName>.
  • NSKVONotifying_<ClassName>Rewrite thesetter,class,deallocTo add_isKVOA.