KVO自定义

1.jpeg

很长一段时间没有更新文章了,主要是自己在边当码农边学习充电(学习不写文章就是偷懒)。最近系统的学了一下Flutter,两年前搞过原生和Flutter混编,不过那时候只是埋头完成工作也没咋上心,这次系统的过了一遍也简单的了解了下Flutter的一些底层实现原理。这个后期也会选择性的出一些文章比如Flutter“异步”"多线程"“增量渲染”“底层原理”等。接下来进入今天的主题。

KVO官方实现的分析

既然在上一篇文章我们探究了系统KVO 的底层实现原理,那我们自己实现也难逃窠臼,照着搞一套呗。所以完全可以仿照系统的API,不管三七二十一搞一套对外的API先。看 图1 系统的API

1.png

系统的KVONSObject的一个分类,然后实现了三个对外的API。分别是添加观察者API移除观察者API观察者值变化接收通知API。这三个API 我们也是熟悉的不能再熟悉的了。不过我们可以发现,系统对这些API的实现还不是在一个分类里。乃是分了两个分类。那我们不需要这么麻烦。我们都放在一个分类里搞定就好。

KVO仿官方自定义实现

1,创建分类仿照系统的API 实现自己的API

我们创建NSObject的分类ZYKVO。并且仿照系统的定义三个对外API 如图2:

2.png
2,根据上节课的底层原理实现自己的API
(1)添加观察者API 实现

我们先来捋一捋添加观察者的步骤:
1: 首先判断进来的观察值是否是实例变量(因为实例变量不在观察范围我们只观察属性)
2: 动态生成子类 并且添加类方法setter方法(完全仿照系统的做法)
3: 改变isa的指向 : 指向我们动态创建的子类ZYKVONotifying_ZYPerson (这个子类的名字也是仿照系统的取名规则)
4: 保存观察者 因为我们在最后还要给观察者发送通知 告诉观察者 值的变化 (这里采用 关联对象 方法保存 因为我们是NSObject 的一个分类)

接下来我们按照上面的步骤来一步一步实现:

1:判断是否是实例变量:

3.png

2: 动态生成子类:

4.png

IMP的自定义方法 zy_classzy_setter:

5.png

到这里我们应该可以拦截到观察的属性值了。我们来验证下:

6.png
7.png
8.png

3: 修改isa的指向,讲父类的isa指向动态创建的子类:

6.png

4: 保存观察者对象和信息:

注意:因为我们的观察者肯定不止一个,所以我们要留有后路保存的必须是一个数组类型的;既然是多个观察者那肯定有多个keyPath 多个option 多个context;那我们不如创建一个信息类来保存这些关联信息,然后我们只需要保存一个个的信息对象数组里,我们就在本文件里创建一个信息类ZYKVOInfo 如图:

7.png
8.png

5: 验证:

下面我们就需要去我们自己定义的zy_setter方法里去实现剩余的步骤,即发消息给父类调用父类的setter方法进行赋值发通知到观察者ViewController告诉观察者已经观察到改变了并且调用观察者实现的观察方法

10.png
11.png
12.png

到这里已经完成一小半了,已经可以成功拦截属性赋值到我们动态生成的子类zy_setter方法里了。

接下来我们就要去实现剩下的步骤了如:实现zy_setter方法去给父类发消息改变父类属性的值,并且发通知给观察者告知KVO的值的变化情况。下面我们就来实现这个方法:

13.png
(2)通知观察者API 实现
14.png
15.png
16.png

验证:

17.png

至此,我们的自定义ZYKVO已经实现了基本功能了,还差最后一步的移除操作。

(3)移除观察者API 实现

移除观察者我们在上一篇文章对KVO分析的时候发现它只是把isa转回到了父类,但是新创建的子类并没有直接删除。我们也只需要如此操作就可以:

18.png

到这里我们基本上实现了基础的自定义KVO功能。不过其中还有很多可以优化的地方,比如在zy_setter方法里开始我们应该判断一下KVO自动开关的问题,因为开关默认是打开的,如果手动关闭则就要做处理,不去进行下面的步骤发消息了;或者说我们是否可以考虑观察者的自动释放功能?是否可以考虑利用函数式编程思想来修改KVO?接下来我们实现下:

KVO函数编程思想优化实现

在前面“仿照官方KVO”的方式自定义实现了基本功能,但是我们发现我们使用的时候观察对象都需要去实现一个观察方法,并且在这个方法里如果有观察多个对象还要进行各种判断来处理业务逻辑。这很麻烦。那我们是否可以考虑利用函数式编程思想来解决这种问题呢?答案当然是可以的,我们在上面自定义的基础上来实现下:

1,根据前面实现的信息保存类ZYKVOInfo进行改造
19.png
20.png
2,根据前面实现的添加观察者API进行改造
21.png
22.png

3, 根据通知观察者API 进行改造

23.png
24.png

验证:

25.png

这样我们就简单的改造了前面定义的KVO 改成了函数式的方法调用和封装。

KVO自动移除观察者优化实现

我们使用观察者都有一个共识就是及时移除观察者,在上一篇文章KVO分析中也有验证不移除的后果。但是每次都要手动移除难免略显麻烦,那我们不禁想是否有办法让它自动移除呢?就如我们借用函数式编程思想来解决添加观察者和监听方法分离的方法呢?我们一起探究下。

思路探究:
1,首先我们一般移除某个对象的观察者方法都是会放在其dealloc方法里。如果我们要实现自动释放观察者,那我们是否可以通过监听到对象的释放调用dealloc方法的时候就自动调用我们的KVO释放方法呢?
2,我们知道不可能用通知啊之类的因为我们是要覆盖所有监听对象的dealloc方法。那我们是否可以在我们自定义的ZYKVO类对外界观察者的dealloc进行某些处理以至于当外界的观察者的dealloc调用的时候我们就能知道呢?
3,首先想到的就是我们常用的黑魔法-方法交换
4,我们以前用方法交换都是在load方法里就直接交换了,但是这里我们仔细思考后发现不能在load方法里实现。原因是我们的ZYKVONSObject类的分类,如果在load方法里拦截和交换就会把所有系统和我们代码所有的类的dealloc方法都被拦截和交换一次。而我们只需要针对观察者的类进行方法交换。
5,那我们是否可以考虑去观察者添加方法里去做交换处理?
6,在观察者添加方法里的去处理的时候我们发现我们还发现有可能对同一个观察者交换多次的问题。
7,在观察者添加方法里交换方法也需要去判断观察者类是否实现了dealloc方法,如果没有实现还要去动态添加然后进行交换。
8,那我们还有其他办法么?我们想到这里不禁想到我们实现的zy_setter方法。我们在前一篇文章中探究KVO的底层原理释放那段的时候发现,当观察者类在走dealloc方法的时候isa的指向还是动态生成的子类,也就是说此时调用的dealloc是动态生成的子类的,那我们直接对动态生成的子类多添加一个dealloc方法不就相当于在观察者走dealloc方法的时候就走了动态子类的dealloc方法么?我们在动态子类添加的dealloc方法去直接释放不就好了么?想到就做,我们试试。

KVO自动释放实现
26.png

验证:

27.png
28.png

至此,文章的主要内容已经全部完成了,基本也实现了我们自己的想法。不过内部还有很多细节和判断其实是需要处理的。不过我这篇文章主要是根据上一篇文章的内容来简单实现一下主要流程。有兴趣的可以去做更多的完善,比如在zy_setter方法里开始我们应该判断一下KVO自动开关的问题,因为开关默认是打开的,如果手动关闭则就要做处理,不去进行下面的步骤发消息了,自动释放KVO的方法还有很多细节值得去优化例如在zy_dealloc调用完通知父类调用dealloc方法等等细节问题。

论到KVO 的自定义以及KVO自动释放的问题,其实有一个框架非常值得我们去学习,就是Facebook开发的框架KVOController

遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。谢谢!

你可能感兴趣的:(KVO自定义)