31

一个RxCocoa简单示例及其实现原理

 5 years ago
source link: https://iweiyun.github.io/2018/11/01/rxcocoa-code/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

RxCocoa是 RxSwift 的一部分,主要是UI相关的Rx封装。比如实现了很多组件的绑定功能,可以把值跟控件之间相互绑定,可以避免写很多通知、修改数据等代码。也可以监听delegate改变,无须把控件创建及delegate处理分开写等。

RxCocoa里面也定义了很多类,专门为UI处理提供的,比如ControlProperty、 ControlEventDriver 、Binder等。RxCocoa可以用好的话,可以极大简化UI相关处理逻辑。但是,要想随心所欲的使用,还是要对其实现要有一定的了解,否则就容易写出不是那么简洁的代码。

比如前几天在一篇博客上,看到的一段代码:

textfield.rx.text
    .asObservable()
    .subscribe { print($0) }
    .disposed(by: disposeBag)

这段代码一眼看过去,是没什么问题的,执行起来看起来也是ok的。

但是,这段代码的问题就是存在冗余。具体为什么冗余,稍后再分析。先从简单的示例了解RxCocoa

简单示例

RxSwift 的源码里面有附带示例代码,源码clone下来之后,打开Rx.xcworkspace,即可以选择示例运行看效果。

现在就拿最简单的Numbers例子看下,(也可以在线看下这个最简单示例代码: Numbers

核心代码就这一段:

Observable.combineLatest(number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty) { textValue1, textValue2, textValue3 -> Int in
		return (Int(textValue1) ?? 0) + (Int(textValue2) ?? 0) + (Int(textValue3) ?? 0)
	}
    .map { $0.description }
    .bind(to: result.rx.text)
    .disposed(by: disposeBag)

这个示例是将三个输入框的内容加起来,绑定在Label上,上面值变化之后,下面的Label立即跟着变化。

为了让例子更简单,我们可以只把label的值绑定在第一个输入框上:

number1.rx.text.orEmpty
    .map { $0.description }
    .bind(to: result.rx.text)
    .disposed(by: disposeBag)

现在我们逐个分析下,这里面的 rx、text、orEmpty、bind到底是什么

rx

number1.rx是表示什么呢?我们根据源码来推导一下

先看下rx属性的源码实现:

public var rx: Reactive<Self> {
    get {
        return Reactive(self)
    }
    set {
        // this enables using Reactive to "mutate" base object
    }
}

实际上是创建了一个Reactive对象,所以:

number1.rx  ===>  Reactive(number1)

Reactive源码又如下:

public struct Reactive<Base> {
    /// Base object to extend.
    public let base: Base

    /// Creates extensions with base object.
    ///
    /// - parameter base: Base object.
    public init(_ base: Base) {
        self.base = base
    }
}

所以number1.rx最终会变成如下代码:

public struct Reactive {
    public let base: UITextField	// 指向number1
}

所以number1.rx是变成了 Reactive结构体 ,此时,Reactive的扩展方法,我们就可以使用了。

所以,这个.rx是进入Rx世界的入口,控件调用.rx属性之后,后面的内容就表示进入了Rx的世界了。

text

上面分析了,number1.rx是一个Reactive的结构体,它后面就可以继续调用Reactive及其扩展的属性和方法了。

(实际上number1.rx只能调用不限定的扩展的方法或者限定Base是UITextField类型的扩展方法)

所以,text属性就是Reactive的扩展的属性。这个属性定义在UITextField+Rx.swift文件中,下面是其简化后的实现:

extension Reactive where Base: UITextField {
    public var text: ControlProperty<String?> {
        return value
    }

    public var value: ControlProperty<String?> {
        // 默认事件支持了allEditingEvents、valueChanged
        return base.rx.controlPropertyWithDefaultEvents(
            getter: { textField in			// 要发出事件时,所需的数据,即是从这儿获取
                textField.text
        	},
            setter: { textField, value in	// 作为监听者时,收到数据时会调到这儿
                if textField.text != value {
                    textField.text = value
                }
        	}
        )
    }
}

text简单的调用了value属性,它们是ControlProperty类型的。

ControlProperty是表示控件属性,即能监听变化,又能发出通知,即同时实现了ObservableType和ObserverType协议,所以控件才能支持 双向绑定

上面代码中的getter块是在作为ObservableType时所使用的,setter块是作为ObserverType所需要的。

具体ControlProperty实现就不展开了,可以在UIControl+Rx.swift查看具体实现。

orEmpty

上面代码中的一个细节:

public var text: ControlProperty<String?>

这个value值传出来之后是String?的,是个可选值。在很多情况下,并不想要可选值,只希望如果为nil时,传””空字符串出来即可。否则可选值出来之后,外面可能处理为nil的情况处理起来会比较繁琐。

orEmpty就是这个作用。

orEmpty的实现如下:

public var orEmpty: ControlProperty<String> {
        let original: ControlProperty<String?> = self.asControlProperty()

        let values: Observable<String> = original._values.map { $0 ?? "" }
        let valueSink: AnyObserver<String> = original._valueSink.mapObserver { $0 }
        return ControlProperty<String>(values: values, valueSink: valueSink)
    }

会根据目前的values构造一个新的values,并最终构造一个新的ControlProperty,去除可选值的情况。

bind

bind即是用来将一个信号发送者和一个信号监听者绑定在一起,即有信号发送,监听者自动收到通知。听起来是跟subscribe做的事情比较类似。

bind(to:)有几个不同的实现,最简单的实现版本如下:

public func bind<O: ObserverType>(to observer: O) -> Disposable where O.E == E {
    return self.subscribe(observer)
}

其实就是订阅的封装。可以理解为为subscribe起了一个在绑定场景下比较容易理解的名字。

在很多情况下,我们的observer并不需要处理error、complete事件,并且处理逻辑需要在主线程中执行。所以RxCocoa帮我们封装好了一个叫Binder的对象,我们使用这个对象时,不需要考虑太多。

RxCocoa提供的一些监听属性,比如UILabel的rx.text属性即是Binder类型的:

extension Reactive where Base: UILabel {    
    public var text: Binder<String?> {
        return Binder(self.base) { label, text in
            label.text = text	// 收到事件就会调用到这儿
        }
    }
}

结尾

这样这个简单示例就串起来了,并且能够很明确每次调用在做什么事情。

现在再看最开始的那段代码:

textfield.rx.text
    .asObservable()
    .subscribe { print($0) }
    .disposed(by: disposeBag)

我们知道textfield.rx.text是ControlProperty的类型,并且这个类型本身就实现了Observable协议的,那么冗余的代码就很明确了,即asObservable()这一次调用是没意义的。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK