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 扩展

RxCocoaUIVIew 添加类型为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 提供的 sentMessagemethodInvoked 方法监听 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
    • 需要了解 OCKVO 的相关概念
    • 语法不够语义化
  • 添加 rx 扩展
    • 需要额外的对 Reactiveextension 的代码,对于不同的 Class 甚至需要多个 extension
    • 需要了解 @objcdynamic#selector相关概念
    • 更加语义化

Reference