为了账号安全,请及时绑定邮箱和手机立即绑定

MG--探究KVO的底层实现原理

标签:
iOS Swift

willChangeValueForKey:\color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:各个参数的作用分别是什么, observer\color{#00DD00}{observer}observer中需要实现哪个方法才能获得KVO\color{#dd0000}{KVO}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−ValueObserving)\color{#dd0000}{KVO(Key-Value Observing)}KVO(KeyValueObserving)

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

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

二、 KVO\color{#dd0000}{KVO}KVO内部实现原理

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

三、如何手动触发一个value的KVO\color{#dd0000}{KVO}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\color{#dd0000}{KVO}KVO的默认实现并进入自定义的KVO\color{#dd0000}{KVO}KVO实现看链接


五、附注: KVC\color{#0000FF}{KVC}KVC底层实现原理(如下)

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

  • 具体主要分为三大步
    第一步寻找该属性有没有setsetter方法有就直接赋值
    第二步寻找有没有该属性带下划线的成员属性有就直接赋值
    第三步寻找有没有该属性的成员属性有就直接赋值
  • 或者这么说
    1、首先搜索setKey:方法.(key指成员变量名, 首字母大写)
    2、上面的setter方法没找到, 如果类方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKeykey, 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");

  • 可供参考文章


面试题

通过修改类的成员变量不会触发KVO\color{#dd0000}{KVO}KVO,那为什么通过KVC\color{#0000FF}{KVC}KVC的setValue:forkey: 给成员变量赋值会触发KVO呢?
首先KVO\color{#dd0000}{KVO}KVO的基础是KVC\color{#0000FF}{KVC}KVC我们看下KVC\color{#0000FF}{KVC}KVC的底层实现
1、首先搜索setKey:方法.(key指成员变量名, 首字母大写)
2、上面的setter方法没找到, 如果类方法accessInstanceVariablesDirectly\color{#00DD00}{accessInstanceVariablesDirectly}accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKeykey, iskey的顺序搜索成员名。
如果找到了就会触发KVO因为底层内部会调用willChangeValueForKey:\color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:\color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:方法。
你可以重写该类的调用willChangeValueForKey:\color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:\color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:方法去验证当KVC改变属性值的时候比如Person继承NSObject它有一个成员变量@public int _age;[self.person1 setValue:@(10) forKey:@“age”]; 会调用willChangeValueForKey:\color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:\color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:方法。所以会触发KVO。
通过修改类的成员变量不会触发KVO因为成员变量不会生成setter方法直接访问成员变量自然不会触发KVO而要触发KVO本质是必须调用调用willChangeValueForKey:\color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:\color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:方法。KVO的底层实现也是通过重写setter方法 setter方法里面调用willChangeValueForKey:\color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:\color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:方法。
KVO内部实现原理



点击查看更多内容
2人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消