zoukankan      html  css  js  c++  java
  • ios KVO的实现原理

    首先给大家介绍一下KVO的使用场景:当某个对象的某个属性改变的时候,需要我们做出相应的处理事件。比如我们自定义下拉刷新,那么我们是如何得知用户要进行的下拉刷新数据操作呢,我们可以监听控件的frame,通过用户下拉该控件的时候,会修改该控件的frame.y属性,我们使用KVO监听这个属性。当这个属性的y值在某个范围,我们认为是下拉刷新操作。我们可以去进行数据请求。因为现在下拉刷新的第三方框架有很多,所以很少有人关注下拉刷新的实现原理。 又比如,我们经常使用的网络请求的第三方AFN,它内部监听网络下载进度也是通过KVO实现的。无凭无据,我们来看代码

    fractionCompleted则是self.downloadProgress对象代表下载进度的属性

    等等。所以KVO的使用之处还是比较多的。那么,我们就看看KVO实现的原理是什么样子的,如何实现对属性的监听的。

    给大家介绍一下官方的回答:当某个类的对象一次被观察时,系统就会在运行时动态的创建该类的一个派生类(子类),在这个派生类重写被观察属性的setter方法,派生类在被重写的setter方法实现真正的通知机制。派生类重写了class方法以欺骗外部调用者它就是起初监听的那个类。然后系统将这个对象的isa指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对setter的调用就会调用重写的setter,从而激活键值通知机制。此外,派生类还重写了dealloc方法来释放资源。

    不知道看完这段话的你,头脑还是否清醒。下面我们一句一句来解释:

    创建一个Person类,定义一个age属性,然后我们监听age属性

    @interface Person : NSObject
    
     
    
    @property (nonatomic , assign) int age;
    
     
    
    @end
    
    @implementation ViewController {
    
        Person *_p;
    
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        //创建Person对象
        _p = [[Person alloc] init];
        
        //给Person对象的age属性添加监听
        [_p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
        
        //修改p的age属性
        _p.age = 10;
        
    }
    
    //KVO回调方法,
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
        NSLog(@"%@对象的%@属性变化了%@",object,keyPath,change);
    
    }
    
    
    //控制器销毁的时候 移除KVO
    - (void)dealloc {
    
        [_p removeObserver:self forKeyPath:@"age"];
    
    }
    

    这个时候,会打印输出

     

    这是KVO使用的步骤。

    接下来,我们理解一下这段代码。

    1.创建一个Person类的实例对象P

    2.给对象P的age属性添加键值监听

    3.修改对象P的age属性

    4.实现KVO的回调方法

    5.移除KVO监听

    从输出结果来看,我们是可以正常监听到P的age属性的变化的。

    根据官方的解释:当某个类的对象一次被观察时,系统就会在运行时动态的创建该类的一个派生类(子类)

    那么,我们看一下这到底是一个什么样的子类:

    看断点断的地方,打印这个结果的时候我还没有对对象P进行KVO监听。有的人会问,objc_getClass()是什么,objc开头的函数基本都是runtime里面的函数。所以这是动态获取对象的类型。

    当对对象P的age属性进行监听以后,对象P的真实类型变为了NSKVONotifying_Person。那么,为什么po _p 还是<Person>类型的呢,这就是‘派生类重写了class方法以欺骗外部调用者它就是起初监听的那个类’。

    接下来,我们再一步步理解。既然有了这个子类了,我们再这个子类的setter方法里面动动手脚,是不是就可以出发KVO的回调了,就会输出监听的结果呢?那么,我们如何让Person类型的P对象,去执行NSKVONotifying_Person类里面的setter方法呢?系统将这个对象(P)的isa指针指向这个新诞生的派生类(NSKVONotifying_Person)。不知道大家对isa指针了解多少?OC所有的类都有一个共同的属性,那就是isa。有图有真相:

    那么。这个isa指针是干什么用的呢。

    isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。

    所以,只要我们修改了isa指针指向的类。就会去它指向的类里面寻找setter方法,也就是会去(NSKVONotifying_Person)类里面寻找setter方法,这个时候我们再NSKVONotifying_Person类的setter方法里面做些事情,从而触发KVO的回调方法,也就是

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
        NSLog(@"%@对象的%@属性变化了%@",object,keyPath,change);
    
    }
    

     是不是就成功了,接下来介绍,我们再setter方法里面具体怎么做,才能触发这个方法呢?

    #import "Person.h"
    
    @implementation Person
    
    
    - (void)test {
    
        [self willChangeValueForKey:@"age"];
        
        [self didChangeValueForKey:@"age"];
    
    }
    
    @end
    

     给Person类添加一个test方法。这个时候,我修改ViewController里面的代码

    @implementation ViewController {
    
        Person *_p;
    
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        //创建Person对象
        _p = [[Person alloc] init];
        
        //给Person对象的age属性添加监听
        [_p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
        
    //调用test方法
        [_p test];
        
    }
    
    //KVO回调方法,
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
        NSLog(@"%@对象的%@属性变化了%@",object,keyPath,change);
    
    }
    
    
    //控制器销毁的时候 移除KVO
    - (void)dealloc {
    
        [_p removeObserver:self forKeyPath:@"age"];
    
    }
    

     这个时候,运行代码,你会发现也会触发KVO的回调用法。可是我根本没有改变age属性的值。怎么会触发呢。归根究底,问题出现在test的实现方法里面,也就是

    [self willChangeValueForKey:@"age"];
        
    [self didChangeValueForKey:@"age"];

    这两句代码是触发KVO的回调方法的关键了。所以,我们只要在NSKVONotifying_Person类里面重写setAge方法。就可以实现KVO监听了。这也就是所谓的“在这个派生类重写被观察属性的setter方法,派生类在被重写的setter方法实现真正的通知机制”;

    - (void)setAge:(int)age {
        
        [self willChangeValueForKey:@"age"];
        
        self.age = age;
        
        [self didChangeValueForKey:@"age"];
    
    
    }
    

    看完这篇文章,如果大家有不理解的地方,可以留言一起讨论。谢谢。

  • 相关阅读:
    Java实现 LeetCode 400 第N个数字
    Java实现 LeetCode 400 第N个数字
    Java实现 LeetCode 399 除法求值
    Java实现 LeetCode 399 除法求值
    Java实现 LeetCode 399 除法求值
    Java实现 LeetCode 398 随机数索引
    Java实现 LeetCode 398 随机数索引
    Java实现 LeetCode 398 随机数索引
    linux中的cd ..和cd -命令有什么区别?
    GCC使用
  • 原文地址:https://www.cnblogs.com/xiaobai51/p/6361525.html
Copyright © 2011-2022 走看看