前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS KVO实现原理及使用

iOS KVO实现原理及使用

作者头像
iOSSir
发布2023-03-19 11:46:35
4410
发布2023-03-19 11:46:35
举报

技术不缺乏缔造者,网络不缺乏键盘侠,但缺乏分享技术的源动力!

关于KVO的实现,文章已经很多了,这里阐述我个人的观点,写一些自己的感受

1、简介

KVO(key-value observe)是在KVC的基础上实现的一种用于监听属性变化的设计模式;如果对某个类的某个属性设置了KVO,那么当这个属性发生变化时,就会触发监听方法,从而知道属性变化了。如果本类一个属性的改变会影响到其他多个属性的变化,我们也会经常自己重写这个属性的set方法,用来监听他的变化,但是如果不是本类的属性,我们就没办法重写其set方法了,这个时候KVO就可以上场了,其实KVO本质上也是重写set方法,而整个过程依赖于runtime才能实现。

2、使用

1)设置监听

代码语言:javascript
复制
// 初始化一个测试类,类里面声明一个属性:nameStr_kvoTest = [[KVOTestModel alloc] init];// 给属性nameStr设置监听,类型不同,回调值不同,这里监听的是新值(NSKeyValueObservingOptionNew),还可以监听其他变化,具体看枚举[_kvoTest addObserver:self forKeyPath:@"nameStr" options:NSKeyValueObservingOptionNew context:nil];

2)实现监听回调方法

代码语言:javascript
复制
// 监听的属性通过kvc发生变化时,执行下面方法- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {    
   // 这里我们只判断了keyPath,如果存在多个kvo,为防止重名,可判断object是否为我们监听的实例:_kvoTest
   if ([keyPath isEqualToString:@"nameStr"]) {        // 我们直接打印新值
       NSLog(@"change: %@",[change objectForKey:NSKeyValueChangeNewKey]);
   }
}

3)使用KVO时应该特别注意移除观察者,否在当类要被释放时会发生崩溃

代码语言:javascript
复制
- (void)dealloc {
   [_kvoTest removeObserver:self forKeyPath:@"nameStr"];
}

这里有一点,提一下,iOS11如果不调用上面的方法,也不会崩溃,亲测,但是iOS10及以下的设备会崩溃。在官方文档中没有查到相关的的说明(在iOS9之后,NSNotification已经不用移除了,可能也是这个趋势吧)。

3、实现原理

KVO是根据iOS runtime实现的,当监听某个对象(_kvoTest)的某个属性时,KVO会创建这个对象的子类,并重写我们监听的属性(keyPath)的set方法,具体实现可能是下面这个样子。

代码语言:javascript
复制
- (void)setNameStr:(NSString *)nameStr {
   [self willChangeValueForKey:@"nameStr"];
   [super setValue:nameStr forKey:@"nameStr"];
   [self didChangeValueForKey:@"nameStr"];
}

didChangeValueForKey:方法执行完之后,KVO会根据注册时option返回不同的字典

上面我们提到了KVO是建立在KVC基础上的,可以看到,如果不通过KVC改变属性,那么set方法就不会执行,那么KVO也就没办法监听属性的变化了。

4、再具体一点(原理)

我们都说他是通过runtime实现的,是因为这个操作是运行的时候动态添加的,可以多阅读一些关于runTime的文章,知道原理,用起来更顺手,也会有更多的想法。

当观察对象时,KVO机制动态创建一个新的名为:NSKVONotifying_对象名 的新类,该类继承自目标对象的本类,且 KVO 为 NSKVONotifying_对象名 重写观察属性的 set 方法。在这个过程,被观察对象的 isa 指针从指向原来的对象,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_对象名 类,来实现当前类属性值改变的监听,这也就是前面所说的“黑魔法”;我还试了一下,创建一个新的名为“NSKVONotifying_对象名”的类,发现系统运行到注册 KVO 的代码时,iOS10及以下会崩溃,iOS11下控制台打印警告:

代码语言:javascript
复制
[general] KVO failed to allocate class pair for name NSKVONotifying_KVOTestModel,
automatic key-value observing will not work for this class

存在同名类,无法进行KVO监听了~。

5 、扩展

很多人都用过YYKit,很强大的开源类库。里面有个扩展类NSObject+YYAddForKVO,里面对kvo做了一下封装,用起来方便好多。他通过运行时,动态的给类添加了属性,存储了所有的keyPath和回调。

三个方法:

代码语言:javascript
复制
/**
Registers a block to receive KVO notifications for the specified key-path
relative to the receiver.
@discussion The block and block captured objects are retained. Call
`removeObserverBlocksForKeyPath:` or `removeObserverBlocks` to release.
@param keyPath The key path, relative to the receiver, of the property to
observe. This value must not be nil.
@param block   The block to register for KVO notifications.
*/- (void)addObserverBlockForKeyPath:(NSString*)keyPath block:(void (^)(id _Nonnull obj, _Nullable id oldVal, _Nullable id newVal))block;
/**
Stops all blocks (associated by `addObserverBlockForKeyPath:block:`) from
receiving change notifications for the property specified by a given key-path
relative to the receiver, and release these blocks.
@param keyPath A key-path, relative to the receiver, for which blocks is
registered to receive KVO change notifications.
*/- (void)removeObserverBlocksForKeyPath:(NSString*)keyPath;
/**
Stops all blocks (associated by `addObserverBlockForKeyPath:block:`) from
receiving change notifications, and release these blocks.
*/- (void)removeObserverBlocks;
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python课后小剧场 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、简介
  • 2、使用
  • 3、实现原理
  • 4、再具体一点(原理)
  • 5 、扩展
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档