转载本文需注明出处:微信公众号EAWorld,违者必究。html
引言:
不管用哪一种语言进行软件开发,咱们都会接触到设计模式,我的认为设计模式存在的意义在于:在某些需求下,采用适合的设计模式,使代码结构合理,从而提升代码的可读性、可扩展性、可移植性,此文将要讨论的是OC开发中的一种经常使用模式之一:观察者模式之KVO。
KVO俗称键值观察(key-value observe),键值观察是当被观察的对象属性发生改变时,会通知到观察对象的一种机制。
目录:
一、KVO的做用
二、KVO的使用方法
三、KVO的实现原理
四、KVO与KVC、代理、通知的区别
五、KVO实现过程当中的注意事项
不管用哪一种语言进行软件开发,咱们都会接触到设计模式,我的认为设计模式存在的意义在于:在某些需求下,采用适合的设计模式,使代码结构合理,从而提升代码的可读性、可扩展性、可移植性,此文将要讨论的是iOS开发中的一种经常使用模式之一:观察者模式之KVO。咱们先看下官方文档给的KVO介绍:
翻译过来就是:KVO是运用isa混写技术实现自动观察键值的。isa指针是指向对象的类,本质上是指向类中的方法实现。当一个对象注册观察者时,这个对象的isa指针被修改指向一个中间类。永远不要用isa来判断一个类的继承关系,而是应该用class方法来判断类的实例。
KVO俗称键值观察(key-value observe),键值观察是当被观察的对象属性发生改变时,会通知到观察对象的一种机制。
1.KVO的做用
一、监听带有状态的基础控件,如开关、按钮等;
二、监听字符串的改变,当监听的字符串改变时,来作一些自定义的操做;
三、当数据模型的数据发生改变时,视图组件能动态的更新,及时显示数据模型更新后的数据,好比tableview中数据发生变化进行刷新列表操做,监听 scrollView的contentOffset属性监听页面的滑动.
2.KVO的使用方法
KVO的使用可分为自动监听和手动监听。
1.自动监听
1.1自动监听操做步骤:
(1)添加观察者
(2)在观察者中添加观察键值方法
(3)在dealloc中移除监听
1.2示例代码:
建立两个类ModelA和ModelB,两个类中都添加属性“des”,在控制器中,将B添加为A的观察者。代码以下:
ModelA中代码:
ModelB中代码:
控制器中代码:
控制器中添加观察者的方法调用的是以下的类方法:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString
*)keyPath options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
各个参数说明:
@param observer 被监听的对象
@param keyPath 被监听对象的属性名,不可为空,为空崩溃
@param options 有4种
(1)NSKeyValueObservingOptionNew 把更改以前的值提供给处理方法
(2)NSKeyValueObservingOptionOld 把更改以后的值提供给处理方法
(3)NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。一般它会带有新值,而不会带有旧值。
(4)NSKeyValueObservingOptionPrior 分2次调用。在值改变以前和值改变以后
@param context 上下文
上述示例代码的运行结果以下所示:
2.手动监听
意思就是说:当某些须要控制监听过程的场景下,就须要手动监听,好比:为了尽可能减小没必要要的触发通知操做,或者当多个更改同时具有的时候才调用属性改变的监听方法。
实现手动监听的要点主要包括这几部分:
a.重写
(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
b.在set方法中在赋值的先后分别调用
willChangeValueForKey和didChangeValueForKey
2.1实现部分属性的手动监听
在animal.h中添加两个属性age和name,在animal.m中关闭age的自动监听功能,其它属性依然能够自动监听,在控制其中实现添加按钮点击按钮的时候改变age的值,并触发监听方法,代码以下:
animal类:
要实现类方法 automaticallyNotifiesObserversForKey,并在其中设置对特定的 key 不自动发送通知(返回 NO 便可)。这里要注意,对其它非手动实现的 key,要转交给 super 来处理[1,2,3]。
控制器:
当不点击按钮的时候,打印结果只打印了name属性的值:
当点击按钮以后,会手动触发监听,打印结果以下:
2.2全部属性都手动监听(禁止自动监听)
若是须要禁用该类KVO的话直接automaticallyNotifiesObserversForKey返回NO。
将animal.m中的类方法修改以后:
运行以后不点击按钮的话,age和name属性都不会自动调用监听方法:
segmentfault
点击了按钮以后,只有实现了手动监听的age属性调用了监听方法:设计模式
3.KVO的实现原理
当某一个类的实例第一次使用KVO的时候,系统就会在运行期间动态的建立该类的一个派生类,该类的命名规则通常是以NSKVONotifying为前缀,以本来的类名为后缀。而且将原型的对象的isa指针指向该派生类。同时在派生类中重载了使用KVO的属性的setter方法,在重载的setter方法中实现真正的通知机制,正如前面咱们手动实现KVO同样。这么作是基于设置属性会调用setter方法,而经过重写就得到了 KVO 须要的通知机制。固然前提是要经过遵循 KVO 的属性设置方式来变动属性值,若是仅是直接修改属性对应的成员变量,是没法实现 KVO 的[4,5]。
4.KVO与KVC、代理、通知的区别
1.与KVC的不一样?
KVC,便是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问对象的属性,而不是经过调用Setter、Getter方法等 显式的存取方式去访问。KVO 就是基于 KVC 实现的关键技术之一。
KVO,即Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,对象就会接受到通知。
2.与delegate的不一样?
和delegate同样,KVO和NSNotification的做用都是类与类之间的通讯。可是与delegate不一样的是:这两个都是负责发送接收通知,剩下的事情由系统处理,因此不用返回值;而delegate 则须要通讯的对象经过变量(代理)联系;delegate只是一对一,而这两个能够一对多。delegate是很是严格的语法,须要定义不少代码。
3.和notification的区别?
notification比KVO多了发送通知的一步。二者都是一对多,可是对象之间直接的交互,notification明显得多,须要notificationCenter来作为中间交互。而KVO如咱们介绍的,设置观察者->处理属性变化,至于中间通知这一环,则隐秘多了,只留一句“交由系统通知”,具体的可参照以上实现过程的剖析。notification的优势是监听不局限于属性的变化,还能够对多种多样的状态变化进行监听,监听范围广,例如键盘、先后台等系统通知的使用也更显灵活方便[6,7]。
5.KVO实现过程当中的注意事项
iOS 10如下会有这些状况,iOS11不会出现这些状况,可是为了代码的严谨性,以及以防出现没法预知的错误,仍是避开这些比较好。
一、添加观察者次数与remove次数不匹配致使程序崩溃
连续对同一属性添加观察者是能够的,可是也要保证在移除观察者的时候也要移除对应次,否则可能会引起崩溃(iOS11以上不会崩溃)。
当对同一个keypath进行两次removeObserver时会致使程序crash,这种状况经常出如今父类有一个kvo,父类在dealloc中remove了一次,子类又remove了一次的状况下。不要觉得这种状况不多出现!当你封装framework开源给别人用或者多人协做开发时是有可能出现的,并且这种crash很难发现。不知道你发现没,目前的代码中context字段都是nil,那可否利用该字段来标识出到底kvo是superClass注册的,仍是self注册的?咱们能够分别在父类以及本类中定义各自的context字符串,而后在dealloc中remove observer时指定移除的自身添加的observer。这样iOS就能知道移除的是本身的kvo,而不是父类中的kvo,避免二次remove形成crash[8]。
二、移除不存在的观察者(iOS11以上不会崩溃)
当某个对象并无添加观察者时,却执行了移除观察者的操做,也会致使程序崩溃,此处不附相关代码。
三、被观察者销毁时还存在观察者(iOS11以上不会崩溃)
这种状况常出如今复杂逻辑下,观察者先于被观察者销毁[9]
四、KVO 行为是同步的,而且发生与所观察的值发生变化的一样的线程上。没有队列或者 Run-loop 的处理。手动或者自动调用 -didChange… 会触发 KVO 通知。
因此,当咱们试图从其余线程改变属性值的时候咱们应当十分当心,除非能肯定全部的观察者都用线程安全的方法处理 KVO 通知。一般来讲,咱们不推荐把 KVO 和多线程混起来。若是咱们要用多个队列和线程,咱们不该该在它们互相之间用 KVO[10]。
参考资料:
[1]https://www.jianshu.com/p/d447660bed7e
[2]https://www.jianshu.com/p/91c41292b5b9
[3]https://www.jianshu.com/p/5a1c58aacb23
[4]https://www.cnblogs.com/yang-shuai/p/8556326.html
[5]https://www.cnblogs.com/PSSSCode/p/5506577.html
[6]http://www.mamicode.com/info-detail-515516.html
[7]https://www.songma.com/news/txtlist_i38955v.html
[8]https://www.cnblogs.com/wengzilin/p/4346775.html
[9]https://segmentfault.com/a/1190000016896055
[10]https://www.jianshu.com/p/b9f020a8b4c9
关于做者:小幸运,iOS软件开发工程师,参与普元Dev客户端OC代码的维护及新功能开发;使用普元移动开发平台开发邮政移动平台项目邮我行app。热爱互联网技术,努力拓宽技术面的程序媛一枚。
关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享。长按二维码关注!安全