为 Class 添加 rx 扩展
extension Reactive where Base: UIView {
var hidden: Observable<Bool> {
return base.rx
.methodInvoked(#selector(setter: Base.isHidden))
.map { event -> Bool in
guard let isHidden = event.first as? Bool else {
return false
}
return isHidden
}.startWith(base.isHidden)
}
}
为Class添加rx扩展
RxCocoa 为 CocoaTouch 中的多个类添加了响应式扩展,如大多数的 UIKit控件,然后就可以响应式编程了。
问题
当想要将某个类的实例的某个属性作为一个被观察时,我们的第一反应可能是下面的代码:
以 UIVIew 的 isHidden 属性为例
aView.rx.isHidden
.subscribe(onNext: { (E) in
})
但是编译器却报错, Value of type 'Binder<Bool>' has no member 'subscribe',为什么呢?
原因
因为rxcocoa 所添加的那些计算属性的类型都是Binder.也就是说这些属性都只能被当做观察者(Observer)使用,而subscribe是只有遵守了 ObservableType协议的实例才拥有的方法。
解决
那么,该如何实现直接订阅相应的属性呢?
使用 KVO
查看了 RxCocoa 的页码后发现有对 KVO 的封装方法:
extension Reactive where Base: NSObject {
/**
Specialization of generic `observe` method.
This is a special overload because to observe values of some type (for example `Int`), first values of KVO type
need to be observed (`NSNumber`), and then converted to result type.
For more information take a look at `observe` method.
*/
public func observe<E: KVORepresentable>(_ type: E.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial], retainSelf: Bool = true) -> Observable<E?> {
return observe(E.KVOType.self, keyPath, options: options, retainSelf: retainSelf)
.map(E.init)
}
}
使用起来非常简单:
UIView().rx
.observe(Bool.self, #keyPath(UIView.isHidden))
.subscribe(onNext: { (isHidden) in
//do something
})
.disposed(by: bag)
但如何实现像上面所说的那样直接对相应的属性进行订阅呢?
aView.rx.isHidden
.subscribe(onNext: { (E) in
// do something
})
为 Class 添加 rx 扩展
和RxCocoa为 UIVIew 添加类型为Binder的计算属性isHidden 一样, 我们为 UIView 添加一个类型为 Observable的计算属性hidden
extension Reactive where Base: UIView {
// note: 这里不是 isHidden 而是 hidden,因为 Rxcocoa 中已经有了一个 isHidden 的计算属性
var hidden: Observable<Bool> {
// 这里暂时先返回 false
return Observable.just(false)
}
}
到这里就可以直接使用上面报错的代码稍加修改就可以使用了:
aView.rx.hidden
.subscribe(onNext: { (E) in
// do something
})
现在来完成 hidden 这个计算属性的实现。怎么实现呢?
每当 isHidden 的值改变时,hidden 都要发射一个相应的值, isHidden 的值改变时会调用其 setter 方法, so, 可以利用 RxCocoa 提供的 sentMessage 或 methodInvoked 方法监听 setter 的调用(sentMessage 在方法调用前, methodInvoked在方法调用后)。
extension Reactive where Base: UIView {
var hidden: Observable<Bool> {
return base.rx
.methodInvoked(#selector(setter: Base.isHidden))
.map { event -> Bool in
guard let isHidden = event.first as? Bool else {
return false
}
return isHidden
}.startWith(base.isHidden)
}
}
对比
- 使用
KVO:- 无需为
Reactive添加extension - 需要了解
OC中KVO的相关概念 - 语法不够语义化
- 无需为
- 添加 rx 扩展
- 需要额外的对
Reactive的extension的代码,对于不同的Class甚至需要多个extension - 需要了解
@objc、dynamic、#selector相关概念 - 更加语义化
- 需要额外的对