KVO explore

KVO generalization

We all know that kvo is a design pattern is a key observation, when the value of the property when the change will trigger a callback, get the old value and the new value of the property. But there may be some friends do not know when to use it, use scene what it is. When you need to monitor a property's value changes when we can use it. such as:

  1. When the picture changed when the url automatically load a new image.
  2. When the offset change scrollView get a callback when the acquisition value of the offset, this time not delegate better, especially the package a time frame, if the delegate so the user may also use it to frame the delegate does not lead to delegate the implementation of the framework, but kvo be no problem.
  3. For example, monitor the progress of the property for some mp3 sound, to draw the UI based on the value of change.
  4. Some such switch monitor to draw UI like. Visible KVO is still very popular, and very practical.

Simple use of KVO

  1. Monitor changes in the value of the name attribute of the Person object p1
//注册kvo
[p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

//属性赋值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    count++;
    p1.name = [NSString stringWithFormat:@"%d",count];
}

//释放
- (void)dealloc{
    [p1 removeObserver:self forKeyPath:@"name"];
}
复制代码
  1. Manual kvo, you can manually control whether to trigger a callback kvo
//重写Person类的automaticallyNotifiesObserversForKey返回NO即关闭了自动kvo
@implementation Person
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    return NO;
}
@end

//注册kvo
[p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

//属性值变化(其实只要该对象的成员变量的值改变即可)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    count++;
    [p1 willChangeValueForKey:@"name"];
    p1.name = [NSString stringWithFormat:@"%d",count];
    [p1 didChangeValueForKey:@"name"];
}

//kvo回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
}

//释放
- (void)dealloc{
    [p1 removeObserver:self forKeyPath:@"name"];
}
复制代码

KVO explore the underlying implementation

Kvo system is how to achieve it? Why change the property as long as the object will trigger the callback it? I am also very curious, before viewing the information that they have thought about how to achieve.
First, the system kvo any object can be called addObserver method, it should be possible to determine the classification of NSObject, and add this addObserver method. The next thought was hook, such as hook Person setter methods corresponding to a class. Achieve access to the exchange value of a member variable of the corresponding property before calling the setter methods, the use of runTime get the old value method. After the re-acquisition after calling the setter method again the value of a member variable of the corresponding property, to get the new value. Then call the old and new values observeValueForKeyPath method passes to observe the corresponding class.

Next View Profile, Oh, My God does not want to own it, the system calls the method when addObserver dynamically creates a new subclass inherits the monitored object corresponding to the class. And rewrite the setter methods of the parent class. Isa and the parent pointer from the object point to the subclass. So that when an object is called the parent class setter method will be called when the setter method subclass, inside the setter method calls willChangeValueForKey, didChangeValueForKey method. After the system calls observeValueForKeyPath method, to pass the new and old values ​​corresponding to oberver class. In addition to the new sub-class overrides the setter methods other than the parent class also overrides the class method, which is calling for outside class time to hide the newly created sub-class. One thing is very strange when we hit a breakpoint after addObserver method and move the mouse cursor to change the subject will find that it actually shows is the parent class instead of a new generation of subclasses. After Logically, we isa pointer to the new sub-class of the object instance should belong to a subclass of fishes.

After then I verify bit, print it isa pointer to the class

    NSLog(@"p1:%@",object_getClass(p1));
    [p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    NSLog(@"p1:%@",object_getClass(p1));
复制代码

Print Results:

2019-01-29 14:28:40.983065+0800 KVOCustom[20946:172543] p1:Person
2019-01-29 14:28:40.983401+0800 KVOCustom[20946:172543] p1:NSKVONotifying_Person
复制代码

Indeed found before addOberver method call is a pointer to the Person class, after the call point to a new class NSKVONotifying_Person, which proves the internal addOberver method is indeed created a new class.
After the course is to understand the new system created inside the class method which implements it. Write your own internal method of printing a class as follows:

- (void)printMethods:(Class)cls{
    unsigned int count;
    Method *methods = class_copyMethodList(cls, &count);
    NSMutableString *strM = [NSMutableString string];
    [strM appendString:[NSString stringWithFormat:@"%@: ",cls]];
    
    for (int i  = 0; i < count; i++) {
        Method method = methods[i];
        NSString *strMethodName = NSStringFromSelector(method_getName(method));
        [strM appendString:strMethodName];
        [strM appendString:@", "];
    }
    NSLog(@"%@",strM);
}
复制代码

Then we call the print method

    [p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    [p1 printMethods:object_getClass(p1)];
复制代码

Print Results:

2019-01-29 14:35:32.435852+0800 KVOCustom[21016:175547] NSKVONotifying_Person: setName:, class, dealloc, _isKVOA,
复制代码

Description inside the newly created class system to achieve the setName method (used to override the parent class's setter methods), class methods (to hide the subclass), dealloc method (release memory), _ isKVOA method (method is kvo system)

When we explore here basically also please Chu implementation kvo system.

in conclusion

  1. System call addObserver method when creating a new internal NSKVONotifying_Person class inherits from the Person class. And changed isa point, point to this new class.
  2. Inside the new class overrides the setter methods of the parent class, so that when the person object name attribute assignment time, that is to call a setter method, which calls the setter method when the child class, this method internally calls the speculation willChangeValueForKey, didChangeValueForKey method, this will call the class corresponding to oberver observeValueForKeyPath method. The old and new values ​​are passed over.
  3. The new class overrides the class method, speculation is entirely in order to hide the implementation subclass. When the person calls the class method when the object will return the Person class instead of a new class guess the internal implementation should look like this:
Class classUse(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}
复制代码
  1. The new class called internal methods _isKVOA, speculated that it was to inform the system uses kvo, internal methods to achieve guess is this:
int _isKVOAUse(id self,SEL _cmd){
    return YES;
}
复制代码

Own handwriting a KVO

Now it is not hands itch to want to own handwriting a KVO friends, now we know the implementation of the system KVO, imitate it ourselves implement a KVO it.

  1. First, determine the type KVO certainly classification NSObject class (since all objects can call addObserver method) method in addObserver its own internal system to mimic a similar write
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath valueChangeBlk:(void(^)(id old, id new))valueChangeBlk{
    //创建子类
    NSString *oldClass = NSStringFromClass(self.class);
    NSString *newClass = [NSString stringWithFormat:@"BSKVONotify_%@",oldClass];
    Class classNew = objc_allocateClassPair(self.class, newClass.UTF8String, 16);
    objc_registerClassPair(classNew);
    object_setClass(self, NSClassFromString(newClass));
    
    //新增set方法
    NSMutableString *strM = [NSMutableString string];
    [strM appendString:[[keyPath substringToIndex:1] uppercaseString]];
    [strM appendString:[keyPath substringFromIndex:1]];
    NSString *setMethod = [NSString stringWithFormat:@"set%@:",strM];
    class_addMethod(NSClassFromString(newClass), NSSelectorFromString(setMethod), (IMP)keyPathMethod,"v@:@");
    
    //新增class方法
    class_addMethod(classNew, NSSelectorFromString(@"class"), (IMP)classUse,"#@:");
    
    //新增_isKVOA方法
    class_addMethod(classNew, NSSelectorFromString(@"_isKVOA"),(IMP)_isKVOAUse, "i@:");
    
    //设置关联对象
    objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_COPY);
    objc_setAssociatedObject(self, "blk", valueChangeBlk, OBJC_ASSOCIATION_COPY);
    objc_setAssociatedObject(self, "classNew", classNew, OBJC_ASSOCIATION_RETAIN);
    objc_setAssociatedObject(self, "classOld", self.class, OBJC_ASSOCIATION_RETAIN);
}
复制代码
  1. As a function of the change in the attribute value corresponding to write a setter method callback
void keyPathMethod(id self,IMP _cmd, id arg){
    //set方法名,原始类和子类
    NSString *keyPath = objc_getAssociatedObject(self, "keyPath");
    NSMutableString *strM = [NSMutableString string];
    [strM appendString:[[keyPath substringToIndex:1] uppercaseString]];
    [strM appendString:[keyPath substringFromIndex:1]];
    NSString *setMethod = [NSString stringWithFormat:@"set%@:",strM];
    Class subClass = objc_getAssociatedObject(self, "classNew");
    Class oldClass = objc_getAssociatedObject(self,"classOld");
    
    //isa指针指向父类,执行set方法
    object_setClass(self, oldClass);
    //获取成员变量的值
    Ivar ivar = class_getInstanceVariable([self class], [NSString stringWithFormat:@"_%@",keyPath].UTF8String);
    id value = object_getIvar(self, ivar);
   // NSLog(@"old:%@",value);
    ((id (*) (id,SEL,id))objc_msgSend)(self,NSSelectorFromString(setMethod),arg);
    id valueNew = arg;
   // NSLog(@"new:%@",valueNew);
    
    //isa指针指向子类
    object_setClass(self, subClass);
    
    void(^blkUse)(id old, id new) = objc_getAssociatedObject(self, "blk");
    if (blkUse) {
        blkUse(value,valueNew);
    }
}
复制代码

The above method can be realized a simple kvo 3. override the class method to hide the internal subclass

Class classUse(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}

复制代码
  1. New method _isKVO
int _isKVOAUse(id self,SEL _cmd){
    return YES;
}
复制代码

The above method can achieve a kvo, and can have it!

Doubts about the system and KVO KVO compare their implementation of

  1. Initially mentioned, kvo system break point when we found the time kvo target system actually is the original parent class instead of a new sub-class. Our kvo target break point found new subclass instead of the original class. That is a very good system of hidden subclasses, and we write kvo can not do, is unclear on this principle.
  2. void keyPathMethod (id self, IMP _cmd, id arg) I have not noticed here write parameter is of type id, where the parameter setter method receives the corresponding parameters passed in. This parameter is the id of the type currently received, so there is a limitation, property type monitor such objects is necessarily object type, and if the basic types will collapse. Also note that the use of time here, and now I do not know how to change, how can receive two types at the same time.
  3. Note circular references
    p1 = [[Person alloc] init];
    __weak typeof(self) weakSelf = self;
    [p1 addObserver:self forKeyPath:@"name" valueChangeBlk:^(id  _Nonnull old, id  _Nonnull new) {
        typeof(weakSelf) self = weakSelf;
        NSLog(@"self:%@,old:%@, new:%@",self,old,new);
    }];
复制代码

Here I use __weak typeof (self) weakSelf = self ; typeof (weakSelf) self = weakSelf; ingenious solution to the problem of circular references. Here I refer MJRefresh source code, so that you can continue to use the self keyword in the internal block. 4. doubts about more than hope someone can answer, thanks! thanks! thanks! Finally, attach the github source code for your reference: github.com/FreeBaiShun...

Reproduced in: https: //juejin.im/post/5cf4b4b3518825095b3322d6

Guess you like

Origin blog.csdn.net/weixin_33850890/article/details/91417414