手动实现KVO

我的Github地址 : Jerry4me, 本文章的demo链接 : JRCustomKVODemo

 


前言

KVO(Key-Value Observing, 键值观察), KVO的实现也依赖于runtime. 当你对一个对象进行观察时, 系统会动态创建一个类继承自原类, 然后重写被观察属性的setter方法. 然后重写的setter方法会负责在调用原setter方法前后通知观察者. KVO还会修改原对象的isa指针指向这个新类.

我们知道, 对象是通过isa指针去查找自己是属于哪个类, 并去所在类的方法列表中查找方法的, 所以这个时候这个对象就自然地变成了新类的实例对象.

不仅如此, Apple还重写了原类的- class方法, 视图欺骗我们, 这个类没有变, 还是原来的那个类(偷龙转凤). 只要我们懂得Runtime的原理, 这一切都只是掩耳盗铃罢了.

以下实现是参考Glow 技术团队博客的文章进行修改而成, 主要目的是加深对runtime的理解, 大家看完后不妨自己动手实现以下, 学而时习之, 不亦乐乎


KVO的缺陷

Apple给我们提供的KVO不能通过block来回调处理, 只能通过下面这个方法来处理, 如果监听的属性多了, 或者监听了多个对象的属性, 那么这里就痛苦了, 要一直判断判断if else if else….多麻烦啊, 说实话我也不懂为什么Apple不提供多一个传block参数的方法

那么, 既然Apple没有帮我们实现, 那我们就手动实现一个呗, 先看下我们最终目标是什么样的 :

简简单单就能让observer监听object的两个属性, 并且监听属性改变后的回调就在对应的callback下, 清晰明了, 何不快哉! Talk is cheep, show you the code!


首先, 我们为NSObject新增一个分类

NSObject+jr_KVO.h

添加观察者

jr_addObserver方法里我们需要做什么呢?

  1. 检查对象是否存在该属性的setter方法, 没有的话我们就做什么都白搭了, 既然别人都不允许你修改值了, 那也就不存在监听值改变的事了
  2. 检查自己(self)是不是一个kvo_class(如果该对象不是第一次被监听属性, 那么它就是kvo_class, 反之则是原class), 如果是, 则跳过这一步; 如果不是, 则要修改self的类(origin_class -> kvo_class)
  3. 经过第二部, 到了这里已经100%确定self是kvo_class的对象了, 那么我们现在就要重写kvo_class对象的对应属性的setter方法
  4. 最后, 将观察者对象(observer), 监听的属性(key), 值改变时的回调block(callback), 用一个模型(JRObserverInfo)存进来, 然后利用关联对象维护self的一个数组(NSMutableArray *)

这段代码还有几个方法, 我们下面一一解释…

首先, setterForGettergetterForSetter, 这两个方法好办. 第一个就是根据getter方法名获得对应的setter方法名, 第二个就是根据setter方法名获得对应的getter方法名

这里需要注意的是, 首字母转换成大写这一项, 不能直接调用NSString的capitalizedString方法, 因为该方法返回的是除了首字母大写之外其他字母全部小写的字符串.

然后, 接下来就是jr_KVOClassWithOriginalClassName:方法了

这个方法还是很直观明了的, 可能不太明白的是为什么要为kvo_class这个类重写class方法呢? 原因是我们要把这个kvo_class隐藏掉, 让别人觉得自己的类没有发生过任何改变, 以前是Person, 添加观察者之后还是Person, 而不是KVO_Person.
这个jr_class实现也很简单.

最后, 重头戏来了, 那就是重写kvo_class的setter方法! Observing也正正是在这里体现出来的.

卧槽, struct objc_super是什么玩意, 卧槽, ((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);这一大串又是什么玩意???

1862021-1a8422453f88e0cc

?????

首先, 我们来看看objc_msgSendobjc_msgSendSuper的区别 :

那么, 很显然, 我们调用objc_msgSendSuper的时候, 第一个参数已经不一样了, 他接受的是一个指向结构体的指针, 于是才有了我们上面废力气创建的一个看似无用结构体

另外, 调用objc_msgSend总是需要做方法的类型强转,


移除监听者

移除监听者就easy easy easy太多了, 直接上代码吧

相信不用注释大家也能看懂, 大家记得在对象- dealloc方法中调用该方法移除监听者就OK了, 否则有可能报野指针错误, 访问坏内存.


监听者信息

JRObserverInfo是个什么模型呢? 这里告诉大家…


运行展示

这里我就简单做个展示, 下面的textLabel监听上面colorView背景色的改变, 点击button, 改变上面colorView的颜色, 然后textLabel输出colorView的当前色

1862021-8405fc669abc9eb1

运行结果

demo可在JRCustomKVODemo这里下载, 同时欢迎大家关注我的Github, 觉得有帮助的话还请给个star~~


参考 :
如何自己动手实现KVO

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

1 5 收藏 4 评论

关于作者:Jerry4me

广工大-大三生 iOS Dev 个人主页 · 我的文章 · 9 ·     

相关文章

可能感兴趣的话题



直接登录
最新评论
  • xiaolinzi   2016/09/20

    为毛最后要异步调用

    • Jerry4me 大三学生 2016/09/20

      因为如果调用set方法的线程恰好是主线程的话, 不异步调用就会造成主线程阻塞

      • xiaolinzi   2016/11/08

        这样的话,接收通知时如果是刷新UI  那必须得切换到主线程喽,如果使用者忘记了,那必然会造成崩溃。

        感觉没必要这样, 个人觉得需不需要异步调用来,应该使用者来决定。

        • Jerry4me 大三学生 2016/11/08

          不对的, 应该这样说明. 因为观察者对象可能不止一个, 那么异步调用显然更有效率. 而且刷新UI的操作本来就应该回到主线程做, 在你使用一些框架的时候遇到这种block回调的情况也会稍加注意看看block调用的代码是否在主线程吧.

跳到底部
返回顶部