观察者模式应该对大多数人都不陌生,iOS中的Notification,KVO都是观察者模式的践行者,甚至GitHub上人气很高的RxSwift,ReactiveCocoa都是对观察者模式的抽象封装。深入了解观察者模式对理解这些框架原理也有裨益。
本篇内容分三部分
一. 概念
二. 特点
三. 实践案例(实现一个监听TextField文字输入事件的阈值化事件流,仿RxSwift throttle操作。)
一.概念
观察者模式定义:
定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知。
基础概念:
在观察者模式中,拥有数据的一方称为主题(subject),依赖数据的一方称为观察者(observer),主题的数据发生变化会通知观察者。
Subject 会实现对应的subject接口,这个接口一般会包含三个方法:addObserver, removeObserver, notifyObserver,分别来用添加、删除观察者以及通知观察者。
Observer 会实现对应的observer接口,这个接口一般会有一个方法用来让Subject调用从而得知变化情况,比如update方法。
观察者模式提供了一种设计,让主题(subject)与观察者(observer)之间松耦合,使对象之间的依赖降到了最低,能让我们建立出更富有弹性的OO系统。主题只关注到观察者实现了某个接口,对具体实现全然不知。主题内部保存有一个观察者的列表,每当发生改变就会逐一通知列表内的观察者。
二.特点
主题(Subject)通知观察者(Observer)有两种方式:
1. push
由Subject主动将数据推给Observer.
例子:update(x: Int, y: String),由于确定了signature,当需要再多传一个参数时,就需要修改很多地方
缺点:Observer被动接收数据,Subject不可能事先预知所有的Observer需要哪些状态,因此Observer可能会受到许多它根本不需要的数据。若把每个变化的状态都推给Observer,显然会出现前面的问题,若不全推,则未来发生需求变化,需要修改很多地方,不够灵活。不过当场景简单的情况下,比如只关注一个状态,此时就很合适,Rx类框架就是采用这种方式。
2. pull
由 Subject 通知Observer状态发生改变(并未直接给Observer数据),observer主动调用Subject 相关get方法获取想要的状态。
例子:update(subject: Subject)或update() ,Observer只需要知道变化已发生,然后直接使用引用的Subject实例的get方法获取想要的属性,即使现在需要多传递参数,只需要给Subject添加一个属性与get方法。
优点:比push方式更灵活,Observer可以仅仅获取自己想要的数据。可以灵活应对以后发生的业务扩展。
三.实践案例
有以下需求:
根据UITextfield中输入的文字搜索结果,为了避免用户快速输入时频繁地发出请求与取消请求,造成客户端与服务器端资源浪费,我们要对触发请求的事件流阈值化,仅当在0.3秒内没有新的触发事件才会将此触发事件通过,否则将重新计时。
最后效果是这样:
下面分四个步骤完成:
1.首先定义Subject, Observer接口
这里用了泛型是为了表示这个接口不止可以使用String,虽然这个案例只用到了String类型。
2. 创建可监听的事件流类,实现Subject接口(MyObservable协议)
下面方法中的counter是类内部的一个自增属性,每当有新Observer,就会用counter的值给它打一个ID,用于移除Observer时定位。类中使用一个Array属性保存了注册的closures。
下面看看里面有哪些属性:
countDown属性用来阈值化事件流,searchStr用来保存键入搜索框的字符串。每当有新的输入将导致原来的计时器重置,若超过0.3秒没有新输入,则会push给所有Observer当前的searchStr,从而触发一次搜索请求。
这里为了便于测试,将阈值设为1s
3. 封装TextField输入事件
至此,已经基本完成,没有说的就差CancelToken了,实现很简单:
4.使用
从控制台打印可以看出,两个observer都成功接收到了事件。当连续快速输入时,中间的输入事件被忽略了。