探究KVO的底层实现原理

作者: Mg明明就是你 | 来源:发表于2016-04-11 23:55 被阅读10213次

addObserver:forKeyPath:options:context:各个参数的作用分别是什么, observer中需要实现哪个方法才能获得KVO回调?

/**
 1. self.person:要监听的对象
 2. 参数说明:
    * @param addObserver  观察者,负责处理监听事件的对象
    * @param forKeyPath 要监听的属性
    * @param  options 观察的选项(观察新、旧值,也可以都观察)
    * @param context 上下文,用于传递数据,可以利用上下文区分不同的监听
 */
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];

/**
 *  当监控的某个属性的值改变了就会调用
 *
 *  @param keyPath 监听的属性名
 *  @param object  属性所属的对象
 *  @param change  属性的修改情况(属性原来的值`oldValue`、属性最新的值`newValue`)
 *  @param context 传递的上下文数据,与监听的时候传递的一致,可以利用上下文区分不同的监听
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%@对象的%@属性改变了:%@", object, keyPath, change);
}

一、KVO (Key-Value Observing)

KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现。也是 Cocoa Binding 的基础。当被观察对象的某个属性发生更改时,观察者对象会获得通知。

有意思的是,你不需要给被观察的对象添加任何额外代码,就能使用 KVO 。这是怎么做到的?

二、 KVO内部实现原理

  • KVO是基于runtime机制实现的
  • 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
  • 如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
  • 每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
  • 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey:didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
  • 补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类


    KVO内部实现原理.png

三、如何手动触发一个value的KVO

  • 自动触发的场景:在注册KVO之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了
  • 想知道如何手动触发,必须知道自动触发 KVO 的原理,见上面的描述
  • 手动触发演示
@property (nonatomic, strong) NSDate *now;

- (void)viewDidLoad
{
    [super viewDidLoad];

    // “手动触发self.now的KVO”,必写。
    [self willChangeValueForKey:@"now"];

    // “手动触发self.now的KVO”,必写。
    [self didChangeValueForKey:@"now"];
}
手动触发一个value的KVO.png

四、补充: 如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?(看链接)


五、附注: KVC底层实现原理(如下)

KVC运用了一个isa-swizzling技术. isa-swizzling就是类型混合指针机制, 将2个对象的isa指针互相调换, 就是俗称的黑魔法.
KVC主要通过isa-swizzling, 来实现其内部查找定位的. 默认的实现方法�由NSOject提供isa指针, 如其名称所指,(就是is a kind of的意思), 指向分发表对象的类. 该分发表实际上包含了指向实现类中的方法的指针, 和其它数据。

  • 具体主要分为三大步
  • 第一步:寻找该属性有没有setsetter方法?有,就直接赋值
  • 第二步:寻找有没有该属性带下划线的成员属性?有,就直接赋值
  • 第三步:寻找有没有该属性的成员属性?有,就直接赋值
  • 或者这么说
  • 1、首先搜索setKey:方法.(key指成员变量名, 首字母大写)
  • 2、上面的setter方法没找到, 如果类方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey,key, iskey的顺序搜索成员名.(NSKeyValueCodingCatogery中实现的类方法, 默认实现为返回YES)
  • 3、如果没有找到成员变量, 调用setValue:forUnderfinedKey:

比如说如下的一行KVC的代码:

  • 举个🌰e.g:

[object setValue:@"13123" forKey:@"uuid"];

就会被编译器处理成:
// 首先找到对应sel
SEL sel = sel_get_ uuid("setValue:forKey:");
// 根据object->isa找到sel对应的IMP实现指针
IMP method = objc_msg_lookup (object->isa,sel);
// 调用指针完成KVC赋值
method(object, sel, @"13123", @"uuid");

可供参考文章

相关文章

  • 2018-02-14

    探究KVO的底层实现原理 addObserver:forKeyPath:options:context:各个参数的...

  • 探究KVC的底层实现原理

    慕课网地址 以前写了关于的实现原理的文章,探究KVO的底层实现原理,现在我们也探究一下的底层实现 原理 的全称是K...

  • iOS面试题整理

    1.探究KVO的底层实现原理 https://www.jianshu.com/p/829864680648 ·KV...

  • Today面试

    Runloop 底层原理Kvo 底层原理ARC 底层原理 如何实现GCD 底层原理Block 底层原理Aut...

  • 探究KVO的底层实现原理

    KVO基本原理:1.KVO是基于runtime机制实现的 2.当某个类的属性对象第一次被观察时,系统就会在运行期动...

  • 探究KVO的底层实现原理

    addObserver:forKeyPath:options:context:各个参数的作用分别是什么, obse...

  • IOS底层(三) KVO底层实现原理

    @[TOC](IOS底层(三) KVO底层实现原理 ) 一,KVO简述 KVO的全称 Key-Value Obse...

  • iOS - KVO

    [toc] 参考 KVO KVC 【 iOS--KVO的实现原理与具体应用 】 【 IOS-详解KVO底层实现 】...

  • KVO的底层实现原理

    KVO的底层实现原理
    KVO的定义(Key-Value Observing) ...

  • 自定义KVO

    导语: 如果对KVO原理不是很熟悉的,可以参考下简书另一篇文章《ios KVO原理探究》,主要是通过模拟KVO底层...

网友评论

  • fightinghawk:你好,问下,通过修改类的成员变量不会触发KVO,那为什么通过KVC的setvalue forkey 给成员变量赋值会触发KVO呢?
    Mg明明就是你:@fightinghawk 我是这样想的:类的成员变量是类吧 而kvo监听者必须是对象吧!对象由类创建。
  • 薛定谔的熊:KVC的原理第二、第三确定没写反?不是先带下划线的,然后不带下划线的?
    Mg明明就是你:@orli_xdx 好的 O(∩_∩)O谢谢
    f48ab9e63152:@Mg明明就是你 2,3确实反了, 先找setter方法, 再找属性,再找_属性, 最后创建属性
  • 不忘初心1990:手动触发KVO这样写了之后改变属性值并没有调用observeValueForKey:ofObject:change:context:,那这叫什么手动触发呢
    f48ab9e63152:@Sunshine_4bd8 right
    Mg明明就是你:@Sunshine_4bd8 :smile:
    32d2543848c0:作者的意思是说 你注册后 不用改变属性的值 只需调用那两个方法 监听照样会执行 这样的

    @property (nonatomic,assign) BOOL kovNoti;

    - (void)viewDidLoad {
    [super viewDidLoad];

    [self addObserver:self forKeyPath:@"_kovNoti" options:NSKeyValueObservingOptionNew context:nil];
    [self willChangeValueForKey:@"_kovNoti"];
    [self didChangeValueForKey:@"_kovNoti"];

    }

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

    if ([keyPath isEqualToString:@"_kovNoti"]) {
    NSLog(@"***************************");
    }
    }

    这样在控制台是有打印的
    2017-08-24 00:29:33.559 Test[3176:209659] ***************************
  • 星好唯柔:一目了然,牛逼
  • 维维豆奶1991:不讲解下KVC么
    RenJK:推荐https://github.com/renjinkui2719/DIS_KVC_KVO
    Mg明明就是你:@维维豆奶招上进代理 KVC是Key Value Coding的简称。它是一种可以通过字符串的名字(key)来访问类属性的机制。具体原理还没研究:sleepy: 有空再去研究一波
  • roylly:KVO讲解的非常透彻清晰,多谢啦!
    Mg明明就是你:@roylly :blush:谢谢

本文标题:探究KVO的底层实现原理

本文链接:https://www.haomeiwen.com/subject/xhwilttx.html