zoukankan      html  css  js  c++  java
  • 浅析 KVO 内部实现

    KVO 全称是Key Value Observing,翻译成键值观察。提供了一种当其它对象属性被修改的时候能通知当前对象的机制。

    KVO 的基本使用:

    (1)注册指定Key路径的监听器:

     /** 参数
             *  addObserver: 监听对象
             *  forKeyPath: 监听属性Key
             *  options: 监听可选项
             *      NSKeyValueObservingOptionNew: 监听改变后的新值
             *      NSKeyValueObservingOptionOld: 监听改变后的旧值
             *  context: 传入的上下文
             */
    - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

    (2)删除指定Key路径的监听器:

    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

    (3)回调监听:

    - (void)observeValueForKeyPath:(NSString *)keyPath //监听的属性值
                          ofObject:(id)object          //监听的对象
                            change:(NSDictionary<NSString *,id> *)change //值的改变(由options参数决定传入新值或者旧值)
                           context:(void *)context //传入的上下文内容
    

    值得注意的是:不要忘记解除注册,否则会导致资源泄露。

    设置属性

    将观察者与被观察者注册好之后,就可以对观察者对象的属性进行操作,这些变更操作就会被通知给观察者对象。注意,只有遵循 KVO 方式来设置属性,观察者对象才会获取通知,也就是说遵循使用属性的 setter 方法,或通过 key-path 来设置:

    target.age = 30;
    [target setAge:30]; 
    [target setValue:[NSNumber numberWithInt:30] forKey:@"age"];

    下面看一个小 Demo:
    我们设想一个场景,当自己住酒店的时候,当酒店给我换房间的时候,我们要得到提醒,才能找对自己的房间。我们依次为例:

    //ViewController
    #import "ViewController.h"
    #import "Person.h"
    #import "Room.h"
    
    @interface ViewController ()
    @property (nonatomic, strong) Person *person;
    @property (nonatomic, strong) Room *room;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.person = [[Person alloc] init];
        self.room = [[Room alloc] init];
        //设置房间的号码
        self.room.no = 10;
    
        //Person 监听 Room 编号的变化
        [self.room addObserver:self.person forKeyPath:@"no" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        //房间号变为20
        self.room.no = 20;
    }
    
    @end
    
    //Person.m 文件
    #import "Person.h"
    
    @implementation Person
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
        if ([keyPath isEqualToString:@"no"]) {
            NSLog(@"Person 检测到 Room 的属性: %@ 值改变: %@", keyPath, change);
        }
    }
    
    //移除观察者对象,防止内存泄漏
    - (void)dealloc{
        [self.room removeObserver:self forKeyPath:@"no"];
    }
    @end

    运行,我们得到:
    结果

    KVO 的内部实现

    下面我们分析下,KVO 的内部实现:
    1> KVO 是基于 runtime 的 isa-swizzing 机制实现的;
    2> 当类 A 的对象第一次被观察的时候,系统会在运行期动态创建类A 的派生类。系统命名为NSKVONotifying_A。
    3> 在派生类 NSKVONotifying_A 中重写类 A 的setter方法,NSKVONotifying_A类在被重写的setter方法中实现通知机制。
    4> 其中键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后,observeValueForKey:ofObject:change:context: 会被调用,继而 didChangeValueForKey: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了(后面介绍)。
    5> 类 NSKVONotifying_A会重写 class方法,将自己伪装成类A。类 NSKVONotifying_A 还会重写 deallo 方法来释放资源。
    系统将所有指向类 A 对象的isa指针指向类 NSKVONotifying_A 的对象。

    为了证明上述过程:我们第一步注释掉ViewController 添加观察者的代码,在运行的时候,查看类 Room 的 isa 指针的值:
    这里写图片描述

    当将添加观察者处的代码打开,我们观察到,在运行的时候,Room 的 isa指针指向了NSKVONotifying_Room 类(派生类)
    这里写图片描述

    KVO 手动实现

    在 Room.m 文件中实现:

    /**
    首先,需要手动实现属性的 setter 方法,并在设置操作的前后分别调用 willChangeValueForKey: 和 didChangeValueForKey方法,这两个方法用于通知系统该 key 的属性值即将和已经变更了;
    
    其次,要实现类方法 automaticallyNotifiesObserversForKey,并在其中设置对该 key 不自动发送通知(返回 NO 即可)。这里要注意,对其它非手动实现的 key,要转交给 super 来处理。
    */
    #import "Room.h"
    
    @implementation Room
    
    - (void)setNo:(int)no{
        [self willChangeValueForKey:@"no"];
        _no = no;
        [self didChangeValueForKey:@"no"];
    }
    
    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
        if ([key isEqualToString:@"no"]) {
            return NO;
        }
        return [super automaticallyNotifiesObserversForKey:key];
    }
    @end

    当我们再次运行观察,发现 Room 的 isa 指针指向Room类:
    这里写图片描述

    参考:

    http://www.cppblog.com/kesalin/archive/2012/11/17/kvo.html

    申明

    以上观点,属于个人的理解,如果错误之处,欢迎拍砖。

  • 相关阅读:
    WPF 柱状图显示数据
    WPF 寻找控件模板中的元素
    WPF 寻找数据模板中的元素
    WPF VisualTreeHelper的使用
    WPF依赖项属性不需要包装属性也可以工作
    WPF依赖属性对内存的使用方式
    WPF Binding Path妙用
    WPF Binding Path妙用代码实现
    WPF Binding妙处-既无Path也无Source
    WPF ListView的使用
  • 原文地址:https://www.cnblogs.com/xiaocai-ios/p/7779759.html
Copyright © 2011-2022 走看看