示例
响应值
首先,从一些命令式代码开始。此示例的目的是如果满足某些条件将从a,b中计算的的值绑定到标识符c。
以下是计算c值的命令性代码:
// 这是标准的命令式代码
var c: String
var a = 1 // 这只会将值“ 1” 值赋给“a”一次
var b = 2 // 这只会将值“2”值赋给“b”一次
if a + b >= 0 {
c = "\(a + b) is positive" // 这只会将值赋给“ c ”一次
}
c的值现在是正数3。但是,如果更改a的值为4,c仍将存储旧值。
a = 4 // `c`仍将等于“3 is positive”,这是不好的,希望`c`等于“6 is positive”,因为4 + 2 = 6
这不是理想的行为。
这是使用RxSwift的改进逻辑:
let a /*: Observable<Int>*/ = BehaviorRelay(value: 1) // a = 1
let b /*: Observable<Int>*/ = BehaviorRelay(value: 2) // b = 2
// 通过组合操作符计数a + b
let c = Observable.combineLatest(a, b) { $0 + $1 }
.filter { $0 >= 0 } // 如果 `a + b >= 0` 为真, `a + b`通过map操作符
.map { "\($0) is positive" } // maps `a + b` 为 "\(a + b) is positive"
// 自从初始化a = 1和b = 2
// 1 + 2 = 3 值 >= 0, 因此`c`初始值等于"3 is positive"
// 要从Rx`Ibservable``c`中提取值,请订阅`c`中的值。
// `subscribe(onNext:)`表示订阅`c`的下一个(新的)值。
// 还包括初始值“3 is positive”。
c.subscribe(onNext: { print($0) }) // 打印"3 is positive"
// 现在,增加`a`的值
a.accept(4)
// 最新值的总和,`4`和`2`,现在是`6`。
// 由于这是`>= 0`,`map`运算符产生“6 is positive正”
// 结果被“赋值”给`c`。
// 由于`c`的值改变了,`{print($ 0)}`将被调用,
// 将打印“6 is positive”。
// 现在改变`b`的值
b.accept(-8) // 不打印任何东西
// 最新值的总和,`4 +( - 8)`,是`-4`。
// 因为这不是`> = 0`,所以`map`不会被执行。
// 这意味着`c`仍然包含“6为正”
// 由于`c`尚未更新,因此尚未生成新的“next”值,
// 和`{print($ 0)}`不会被调用。
简单的UI绑定
let subscription/*: Disposable */ = primeTextField.rx.text // 类型是Observable <String>
.map { WolframAlphaIsPrime(Int($0) ?? 0) } // 类型是Observable <Observable <Prime >>
.concat() // 类型是Observable <Prime>
.map { "number \($0.n) is prime? \($0.isPrime)" } // 类型是Observable<String>
.bind(to: resultLabel.rx.text) // 返回Disposable解除绑定
// 将设置`resultLabel.text`为"number 43 is prime? true" 然后调用completes. 手动触发控制事件,因为这是RxCocoa能观察到的UIKit事件
primeTextField.text = "43"
primeTextField.sendActions(for: .editingDidEnd)
// ...
// 解除绑定
subscription.dispose()
代码分析:
- 不是绑定到Relays,而是使用rx.text绑定UITextField属性值。
- 接下来,map将String转换为Int并使用异步API确定是否是素数。
- 如果在异步调用完成之前更改了文本,则新的异步调用将通过concat替换它。
- 将结果绑定到UILabel。
本例中使用的所有操作符与第一个示例中使用的操作符相同。没什么特别的。
自动输入验证
如果是Rx的新手,下一个例子起初可能会有点压力。但是,它是为了演示RxSwift代码在现实中的表现。
此示例包含具有进度通知的复杂异步UI验证逻辑。disposeBag销毁时,所有操作都将被取消。
试一试:
enum Availability {
case available(message: String)
case taken(message: String)
case invalid(message: String)
case pending(message: String)
var message: String {
switch self {
case .available(let message),
.taken(let message),
.invalid(let message),
.pending(let message):
return message
}
}
}
// 直接绑定UI控件值
// 使用`usernameOutlet`中的用户名作为用户名值源
self.usernameOutlet.rx.text
.map { username -> Observable<Availability> in
// 同步验证,这里没什么特别的
guard let username = username, !username.isEmpty else {
// 方便构建同步结果。
// 如果在同一个方法内部有混合的同步和异步代码,这将构造一个立即解析的异步结果。
return Observable.just(.invalid(message: "Username can't be empty."))
}
// ...
// 用户界面应该在异步操作时显示某些状态
// 正在执行
let loadingValue = Availability.pending(message: "Checking availability ...")
// 这将触发服务器调用以检查用户名是否已存在。
// 它的类型是`Observable <Bool>`
return API.usernameAvailable(username)
.map { available in
if available {
return .available(message: "Username available")
}
else {
return .taken(message: "Username already taken")
}
}
// 使用`loadingValue`直到服务器响应
.startWith(loadingValue)
}
// 因为现在有了`Observable <Observable <Availability >>`
// 需要以某种方式返回一个简单的`Observable <Availability>`。
// 可以使用`concat`操作符如第二个例子,但如果真的想提供一个新的用户名,取消挂起的异步操作。
// 这就是`switchLatest`的作用。
.switchLatest()
// 现在需要以某种方式将其绑定到用户界面。
// 好`subscribe(onNext :)可以做到这一点。
// 于是'Observable`链的结束。
.subscribe(onNext: { [weak self] validity in
self?.errorLabel.textColor = validationColor(validity)
self?.errorLabel.text = validity.message
})
// 这将生成一个`Disposable`对象,可以取消绑定所有内容并取消挂起的异步操作。
// 而不是手动执行,这很乏味,
// 让在视图控制器dealloc上自动处理所有内容。
.disposed(by: disposeBag)