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"];
    
    
    }
    

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

  • 相关阅读:
    使用PHP获取用户客户端真实IP的解决方案
    PHP中使用mkdir创建多级目录的方法
    javascript中将字符串转换为json格式的三种方法
    Codeigniter处理用户登录验证后URL跳转
    PHP正则表达式匹配URL中的域名
    开源项目列表
    PG JDBC COPY感谢原作者
    if中return的用法
    读数据库查询的 ResultSet时java.sql.SQLException: 流已被关闭
    一篇讲JAVA JDBC的好文章
  • 原文地址:https://www.cnblogs.com/xiaobai51/p/6361525.html
Copyright © 2011-2022 走看看