1. KVO简介
在iOS开发中,苹果提供了许多机制给我们进行回调。KVO(key-value-observing)是一种十分有趣的回调机制,在某个对象注册监听者后,在被监听的对象发生改变时,对象会发送一个通知给监听者,以便监听者执行回调操作。最常见的KVO运用时监听UIScrollView对象的contentOffset属性,来完成用户滚动时动态改变某些空间的属性实现效果,例如导航栏颜色渐变、下啦刷新等效果。
2. KVO使用
KVO的使用非常简单,使用KVO的要求时对象能支持KVC -- 在OC和swift中,所有NSObject子类都支持KVC。例如上述的导航栏例子,我们为tableView添加一个监听者viewController,在滑动列表的时候,会计算当前列表的滚动偏移量,然后改变导航栏的背景色透明度,如下代码所示,
override func viewDidLoad() {
super.viewDidLoad()
tableView.addObserver(self, forKeyPath: "contentOffset", options: [.new, .old], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
let offsetY = tableView.contentOffset.y
let delta = offset / 64.0 + 1.0
self.navigationBar.alpha = delta
}
3. KVO的弊端
使用KVO有其方便之处,不过显而易见的弊端在于要写太多的代码,特别是要观察多个对象值的改变的时候,开发者需要在回调方法中写很多的if-else判断,比较好的方法是将KVO的代码封装起来;另外一点就是KVO只能用在NSObject的子类中,这在swift中有一定的局限性,因为swift中不仅有引用类型,还有诸如struct和enum这样的值类型,而KVO不能作用于这些类型上面。
4. 使用RxSwift编写KVO代码
RxSwift提供了很多的组件和对象,开发者可以通过RxSwift来实现KVO的功能,主要有如下3中方式,
4.1 使用rx.observe
现假设有一个Person
数据结构,如下代码定义,
class Person: NSObject {
var name: String = ""
var height: CGFloat = 1.80
var address: String = ""
}
使用rx.observe的代码如下所示,
let p1 = Person()
p1.name = "flion"
p1.height = 190
p1.address = "china"
p1.rx.observe(String.self, "address").subscribe(onNext: { value in
print("new address is \(value)")
}).disposed(by: disposeBag)
p1.address = "usa"
使用rx.observe同样存在着原生KVO所面临的问题,即rx.observe只能作用于NSObject子类的class类型,而不能作用于struct和enum。所以为了能更通用一点,再继续看下面的方法吧。
4.2 使用PublishSubject
如果你也用过ReactiveCocoa,桥接非RAC世界的东西到RAC世界,那么你应该了解Subject,显然RxSwift中也有Subject这样的概念。配合swift的didSet方法,可以如下代码所示使用Subject,
struct Person {
var name: String = ""
var height: CGFloat = 180
var address: String = "" {
didSet {
addressSubject.onNext(address)
}
}
var addressSubject = PublishSubject()
}
let p2 = Person()
p2.name = "flion"
p2.height = 190
p2.address = "china"
p2.addressSubject.asObservable().subscribe(onNext: { value in
print("new address is \(value)")
}).dispose(by: disposeBag)
p2.address = "usa"
4.3 使用Variable
在RxSwift中,还提供了一个更好的解决方案,那就是Variable,如下代码所示,
struct Person {
var name: String = ""
var height: CGFloat = 190
var address: String = "" {
get {
return addressVariable.value
}
set {
addressVariable.value = newValue
}
}
var addressVariable = Variable("")
}
可以看出来,使用Variable,代码简洁了很多,笔者比较提倡这种写法。
5. 关于RxSwift实现KVO的总结
在上述的范例中,我们用到了RxSwift中的Subject和Variable等概念,读者朋友可能有点迷惑,在此,笔者补充一点简单的概念,希望能够给读者解答一些疑惑。
5.1 关于Subject
在RxSwift中Observable是可观察的序列,Observable就像是一个水管,会源源不断地冒出水来。Subject就像一个水龙头,它可以套在水管上,接收Observable上面的事件;但是作为水龙头,它的下游还可以被其他的Observer订阅,即subscribe。也就是说Subject既可以像Observable一样发送事件,也可以像Observer一样订阅事件。
在RxSwift中有3种Subject,分别是,
- PublishSubject 它仅仅会发送observer订阅之后的事件,也就是说如果sequence上有.Next的到来,但这个时候某个observer还没有订阅它,这个observer就收不到这条信息,它只会收到它订阅之后发生的事件。
- ReplaySubject 它和PulishSubject不同之处在于它不会漏消息,即使observer在订阅的时候已经有事件发生过了,他也会收到之前的事件序列。
- BehaviorSubject 当有observer在订阅一个BehaviorSubject的时候,它首先将会收到Observable上最近发送一个信号(或者是默认值),接着才会收到Observable上会发送的序列。
5.2 关于Variable
Variable是对BehaviorSubject的封装,它和BehaviorSubject不同之处在于,不能向Variable发送.Complete和.Error,它会在生命周期结束被释放的时候自动发送.Complete。