老调重弹:对kvo的封装思路
关于kvo,kvo能做什么?
kvo作为cocoa框架的重要特性之一,在底层框架中被大量使用。在特定的场合使用该特性往往能够带来难以想象的好处,让整个方案变得相当简洁和优雅。比如大名鼎鼎的下拉刷新的svpulltorefresh框架,其实现采用了category动态添加属性和kvo结合的方案,在egoRefresh框架的基础上获得了极大的改善,使调用者所要书写的代码量直接下降了一个量级。其中的奥秘在于通过kvo很好的处理了frame变化的问题,调用者不用再处理frame相关的代码,仅需要聚焦下拉刷新带来的业务逻辑事件本身。此后所有流行的下拉刷新框架无一例外的都采用了此方案。仅仅想象一下kvo本身的灵活性、广泛的适用性而言,其强大毋庸置疑;但是如何把它用好,则是一个很值得研究的课题。
直接使用kvo的麻烦?
麻烦1:
对于kvo来说,我认为他最大的坑在于生命周期的管理。举个例子一个对象A一旦发起了对别的对象属性的观察以后,在A对象释放之前,必须要手动调用解除监听的代码;否则在该对象属性变化时,则会引起crash。其实delegate以前也有类似的问题,但在iOS的演进过程中这个问题被解决了。解决方案是这样:在ARC以后delegate应该被设置成weak属性,这样一来当代理者被释放时,雇主的delegate属性是weak的情形中,这个delegate属性会被自动置为空,向一个空对象发消息,不会有任何问题。但是kvo的特性比较古老,苹果也未对其实现做任何改进,因此生命周期的管理对于合理的使用kvo非常重要。
麻烦2:
在另外一种场景下的使用也会crash:当一个对象A在没有发起观察之前就调用解除监听的代码时。所以很多人在调用解除监听的代码时都会引入try-catch来捕获这个错误,从而增加程序的健壮性。这也是一个不得已而为之的无奈之举。
麻烦3:
其他: kvo的调用代码本身略显繁琐,所有的kvo处理代码都被指定在一个方法内部,这样在监听多个对象时会导致该方法内部相当混乱,且该处理方法和调用方法一样,拥有4个参数,其中2个参数几乎可以说是没有作用(一般会被固定填写相同的值); 其中对于属性字段传递的参数是通过字符串来表示的,如果拼写错误也容易带来问题且难以检查(编译器不会告警)。这也是经常为人吐槽或者诟病的地方;人们总希望api清晰简洁,如果能够支持像block这样的现代语法则更棒。
kvo的封装思路
如上,我们认为kvo对于我们的技术链来说是一个不可或缺的环节;但是直接使用它的话,又有一些麻烦需要解决。所以,我们需要对它进行封装,封装的目的就是为了解决上面所列举的麻烦。
解决麻烦1:封装一个对象(我们将它看作是一个专门处理kvo的代理),专门用来管理属性的监听事件;此对象和一次监听动作唯一绑定;在该代理的dealloc里面我们会自动的调用解注册的方法。
解决麻烦2:在此前封装的代理对象中,我们会记录在此对象中的监听事件,如此一来我们自动调用的解注册方法和对外提供的解注册方法都能够保证安全(监听和解监听是一一对应的关系,且在时序上是监听在前,解监听在后)。
解决麻烦3: 封装api方法,支持block或target action的操作,并去除冗余参数。实际需要监听kvo的对象持有上面封装的代理对象,并从代理对象转发回调block或sel方法到实际需要监听kvo的对象。
这样一来我们就完成了一个简单的封装。我们还可以通过category和一些runtime技术的结合,来使得我们的api更简洁,使用更方便;这里就不再展开了。
More:封装示例+Demo
https://github.com/X-Team-X/SimpleKVO
上面的库是笔者从NSObject+YYAddForKVO简单修改而来,感谢ibireme对开源所作的贡献!
YYKit 原地址(author ibireme): https://github.com/ibireme/YYKit
参考和致谢
http://www.cocoachina.com/industry/20131106/7303.html
http://nshipster.com/key-value-observing/
https://github.com/th-in-gs/THObserversAndBinders
https://github.com/ibireme/YYKit/blob/master/YYKit/Base/Foundation/NSObject%2BYYAddForKVO.h
本文的写作参考了上述的文章和开源代码,也在此对其作者们表达感谢。
老调重弹:对kvo的封装思路的更多相关文章
- C#利用Emit反射实现AOP,以及平台化框架封装思路
C#利用Emit反射实现AOP,以及平台化框架封装思路 这是前两天扒的一段动态代理AOP代码,用的Emit反射生成子类来实现代理模式,在这里做个小笔记,然后讨论一下AOP框架的实现思路. 首先是主函数 ...
- AFNetworking封装思路简析
http://blog.csdn.net/qq_34101611/article/details/51698473 一.AFNetworking的发展 1. AFN 1.0版本 AFN 的基础部分是 ...
- 【SSM 7】Mybatis底层封装思路
一.基本概述 在前面的博客中介绍到Mybatis的逆向生成工具,为我们生成了每个实体的基本增删改查的代码,那么每个实体都是那么多的代码,我们很容易的发现,有很大的相似性.对于这部分代码,应该予以抽象封 ...
- jquery插件封装思路整理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- python+unittest框架整理(一点点学习前辈们的封装思路,一点点成长。。。)
预期框架整理目标: 1.单个用例维护在单个.py文件中可单个执行,也可批量生成组件批量执行 2.对定位参数,定位方法,业务功能脚本,用例脚本,用例批量执行脚本,常用常量进行分层独立,各自维护在单独的. ...
- appium 元素文件 -查找元素 封装思路和方法
方法1. try: target="//android.widget.TextView[@text='立即體驗']" element = WebDriverWait(dr,5,0. ...
- iOS的KVO使用和轻量级封装
KVO的使用方法 注冊 [object addObserver:observer forKeyPath:@"text" options:NSKeyValueObservingOpt ...
- Vue + Element-ui实现后台管理系统(4)---封装一个ECharts组件的一点思路
封装一个ECharts组件的一点思路 有关后台管理系统之前写过三遍博客,看这篇之前最好先看下这三篇博客.另外这里只展示关键部分代码,项目代码放在github上: mall-manage-system ...
- 封装的ajax请求
在做登录注册这类提交表单数据时,我们经常需要局部刷新网页来验证用户输入的信息,这就需要用到ajax请求,我们通常需要获取表单中的数据,发起ajax请求,通过服务程序,与数据库的数据进行比对,判断信息的 ...
随机推荐
- JNI NDK开发Crash错误定位 调试
总结: 搜索backtrace 然后: $ /d/android-ndk-r10c/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86 ...
- 简单几何(直线与线段相交) POJ 1039 Pipe
题目传送门 题意:一根管道,有光源从入口发射,问光源最远到达的地方. 分析:黑书上的例题,解法是枚举任意的一个上顶点和一个下顶点(优化后),组成直线,如果直线与所有竖直线段有交点,则表示能穿过管道. ...
- Unity3D shader脚本
Unity中的shader脚本,是用叫做shaderlab的脚本语言来写.这个unity中的shader不只是DX中HLSL写的顶点和像素shader,而应该说是对应着DX中的Effect脚本,定义了 ...
- intro.js 页面引导简单用法
下载地址:http://pan.baidu.com/share/link?shareid=1894002026&uk=1829018343 <!DOCTYPE HTML PUBLIC & ...
- SSH整合所需的jar包
Spring3.1+Hibernate3+Struts2的最新整合所需要的jar包 Spring的基本jar包: 1.org.springframework.web-3.1.4.RELEASE.jar ...
- SSH配置动态数据源
用到一个项目,需要整合2个不同的数据库! 现将代码贴下,以备后用: 1.创建静态映射类,该类映射动态数据源 public class DataSourceMap { public static fin ...
- mac 下的 top 命令
mac 下的 top 命令 文章目录 以前只是在 linux 机器上使用 top 命令.常用的快键键是: p 键 - 按 cpu 使用率排序 m 键 - 按内存使用量排序 这 2 个快捷键在 mac ...
- 【C语言】15-预处理指令1-宏定义
预处理指令简介 1.C语言在对源程序进行编译之前,会先对一些特殊的预处理指令作解释(比如之前使用的#include文件包含指令),产生一个新的源程序(这个过程称为编译预处理),之后再进行通常的编译 2 ...
- 在openGL中绘制图形
点的绘制.: glVertex*():星号表示函数要有后缀 该函数 需要放在glBegin函数和glEnd函数之间,glBegin函数的向量指定绘制图元的类型,而glEnd函数没有参数,例如: glB ...
- 3种用组策略将域帐号加入本地管理员组的方法_jinifly_新浪博客
次当前系统域帐号是怎么在第一次登录时,自动加入域客户端本地管理员组的?我猜不外乎就是脚本.计算机策略或虚拟机初始化的自动应答脚本,结果系统的前任同事找到了答案--GPO的用户策略(确切讲是用户首选项) ...