• KVO的底层实现


    1、KVO是基于Runtime机制实现的;

    2、当某个类的对象的某个属性第一次被观察时,系统会在运行期间动态地创建该类的一个派生类,在这个派生类中重写基类的任何被观察属性的setter方法,派生类在被重写的setter方法内实现真正的通知机制;

    3、如果原类为Person,那么生成的派生类名为NSKVONotifying_Person;

    4、我们知道,每一个对象都有一个isa指针(即第一个成员变量)指向当前类,所以系统就是在当一个类的对象第一次被观察的时候,偷偷的将isa指针指向动态生成的派生类,从而在 “被监听属性” 赋值时执行的是派生类的setter方法;

    5、此外,苹果还偷偷重写了此派生类的class方法,这时候 po personObj,打印的依旧是Person,但使用po object_getClass(personObj),就可以打印出对象的类型NSKVONotifying_Person;这是苹果为了隐藏生成的派生类,让我们误以为还是使用的当前类;

    6、键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就会记录旧的值;而当改变发生后,didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。

    7、注意:willChangeValueForKey:和didChangevlueForKey:缺一不可;

    直接拿代码来说明吧,说了一大堆,估计大家都看的不知所以然。

    1、场景:我们声明了一个Person类,里面有两个属性,name和age,我们要使用kvo来监听Person对象的age发生的变化;

    2、创建Person类,声明属性:

     1 #import <Foundation/Foundation.h>
     2 
     3 /**
     4  Person模型类
     5  */
     6 @interface Person : NSObject
     7 
     8 /**
     9  姓名
    10  */
    11 @property(nonatomic,copy) NSString *name;
    12 
    13 /**
    14  年龄
    15  */
    16 @property(nonatomic,assign) NSInteger age;
    17 
    18 @end

    3、创建Person对象,添加KVO监听对象age属性

     1 #import "ViewController.h"
     2 #import "Person.h"
     3 
     4 @interface ViewController ()
     5 
     6 @end
     7 
     8 @implementation ViewController
     9 
    10 - (void)viewDidLoad {
    11     [super viewDidLoad];
    12     
    13     Person *per = [[Person alloc] init];
    14     per.name = @"xiaoming";
    15     per.age = 18;
    16     
    17     // 观察per对象的age属性的变化情况
    18     [per addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew  context:NULL];
    19     
    20     // 修改age属性的值,将会被观察到
    21     per.age = 20;
    22     
    23     // 这里po per的结果:<Person: 0x608000026960>
    24     // 但是:po object_getClass(per)的结果:NSKVONotifying_Person
    25     // 所以:苹果偷偷重写了派生类的class方法,但用运行时可以查看当前对象所属类型
    26     
    27     // 移除KVO
    28     [per removeObserver:self forKeyPath:@"age"];
    29 }
    30 
    31 /**
    32  KVO监听:当被监听对象的被监听属性变化后调用
    33  
    34  @param keyPath 被监听的属性
    35  @param object 被监听的对象
    36  @param change 变化情况
    37  @param context 携带的参数,此示例传入NULL即可
    38  */
    39 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    40 {
    41     NSLog(@"%@对象的%@属性发生变化了%@",object,keyPath,change);
    42     
    43     // 打印结果:
    44     // <Person: 0x608000026960>对象的age属性发生变化了{
    45     //     kind = 1;
    46     //     new = 20;
    47     // }
    48 }
    49 
    50 
    51 @end

    其实,实现KVO的是这2个方法:willChangeValueForKey:和didChangevlueForKey:

    我们只要调用这两个方法,属性值不变化,也会被观察到。

    1、给Person类添加一个方法kvoTest:

     1 #import <Foundation/Foundation.h>
     2 
     3 /**
     4  Person模型类
     5  */
     6 @interface Person : NSObject
     7 
     8 /**
     9  姓名
    10  */
    11 @property(nonatomic,copy) NSString *name;
    12 
    13 /**
    14  年龄
    15  */
    16 @property(nonatomic,assign) NSInteger age;
    17 
    18 /**
    19  监听age属性发生变化
    20  */
    21 - (void)kvoTest;
    22 
    23 @end
     1 #import "Person.h"
     2 
     3 @implementation Person
     4 
     5 - (void)kvoTest{
     6     
     7     [self willChangeValueForKey:@"age"];
     8     
     9     [self didChangeValueForKey:@"age"];
    10 }
    11 
    12 @end

    2、调用kvoTest方法监听对象属性age的变化:

     1 #import "ViewController.h"
     2 #import "Person.h"
     3 
     4 @interface ViewController ()
     5 
     6 @end
     7 
     8 @implementation ViewController
     9 
    10 - (void)viewDidLoad {
    11     [super viewDidLoad];
    12     
    13     Person *per = [[Person alloc] init];
    14     per.name = @"xiaoming";
    15     per.age = 18;
    16     
    17     // 观察per对象的age属性的变化情况
    18     [per addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew  context:NULL];
    19     
    20     // kvoTest里面调用了willChangeValueForKey:和didChangeValueForKey
    21     // 所以:不改变age的值,依旧会被监听到
    22     [per kvoTest];
    23     
    24     // 这里po per的结果:<Person: 0x608000026960>
    25     // 但是:po object_getClass(per)的结果:NSKVONotifying_Person
    26     // 所以:苹果偷偷重写了派生类的class方法,但用运行时可以查看当前对象所属类型
    27     
    28     // 移除KVO
    29     [per removeObserver:self forKeyPath:@"age"];
    30 }
    31 
    32 /**
    33  KVO监听:当被监听对象的被监听属性变化后调用
    34  
    35  @param keyPath 被监听的属性
    36  @param object 被监听的对象
    37  @param change 变化情况
    38  @param context 携带的参数,此示例传入NULL即可
    39  */
    40 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    41 {
    42     NSLog(@"%@对象的%@属性发生变化了%@",object,keyPath,change);
    43     
    44     // 打印结果:
    45     // <Person: 0x608000032ca0>对象的age属性发生变化了{
    46     //     kind = 1;
    47     //     new = 18; // 因为age的值根本没有发生变化,所是这里的new还是18
    48     // }
    49 }
    50 
    51 
    52 @end
  • 相关阅读:
    asp.net2.0 Theme and Skin
    Microsoft Exchange Server 2010 介绍
    Visual Studio 2010 Team System 动手实验室
    WCF中的消息契约
    Windows Workflow Foundation实验01——Windows Workflow Foundation 快速入门(练习二)
    C#中Brush、Color、String相互转换
    VS自动生成有参构造函数并自动依赖注入插件
    C#集合已修改:可能无法执行枚举操作
    Docker安装后启动不了,报“参考的对象类型不支持尝试的操作”
    windows下安装Docker超全图文教程
  • 原文地址:https://www.cnblogs.com/panda1024/p/6020867.html
Copyright © 2020-2023  润新知