我对RxSwift和RxCocoa还是陌生的,最近我大量使用了Variable
,因为通过其值将突变推入Variable
是多么方便。现在已经弃用了,我试图了解如何最好地使用BehaviorRelay
。有一种Rx-y方式可以做我想做的事,但是我很难下定决心。
[我想要的是将基于结构的模型的实例放在ViewModel后面,观察它的变化并绑定UI元素,这样我就可以通过BehaviorRelay
对该模型进行修改。
模型很简单:
struct Pizza {
var name: String
var price: Float
}
视图模型也是:
final class PizzaViewModel {
let pizzaRelay = BehaviorRelay<Pizza>(value: Pizza(name: "Sausage", price: 5.00))
init(pizza: Pizza) {
pizzaRelay.accept(pizza)
// I feel like I'm missing something Rx-like here...
}
}
然后您可能会在某个地方像这样将UITextField绑定到BehaviorRelay:
viewModel
.pizzaRelay
.asObservable()
.map { $0.name }
.bind(to: nameTextField.rx.text)
.disposed(by: disposeBag)
问题就变成了:如果您需要将文本字段中的值推回到BehaviorRelay中,那应该如何工作?
nameTextField
.rx
.controlEvent([.editingChanged])
.asObservable()
.subscribe(onNext: { [weak self] in
guard let self = self else { return }
if let text = self.nameTextField.text {
self.viewModel.pizzaRelay.value.name = text // does not compile because value is a let
}
}).disposed(by: disposeBag)
我可能在这里没有使用正确的类型,或者我没有考虑正确的输入/输出流的Rx时尚术语,但是我很好奇其他人如何解决这个问题?
我考虑过的其他事项:
Pizza
中的当前值在.subscribe
中重新构造一个新的BehaviorRelay
,更改名称,然后.accept
将其返回到继电器中。但是,那感觉并不完全正确。BehaviorRelay
上为每个属性创建单个Pizza
,然后为每个属性.accept
输入值,然后在所有这些中继上使用combineLatest
并返回Observable<Pizza>
。但这也感觉很笨拙。在理想的世界中应该如何工作?我在想这个吗?救命!我的头很痛。
在理想的世界中,您不会使用中继甚至主题来编写此类代码。与其从结构开始,不如从流程开始。数据应如何在您的系统中移动?
作为示例,这是一个具有视图模型的视图控制器,可以将华氏温度转换为摄氏温度并返回:
struct TempInOut {
let fahrenheit: Observable<String>
let celsius: Observable<String>
}
func tempViewModel(input: TempInOut) -> TempInOut {
let celsius = input.fahrenheit
.compactMap { Double($0) }
.map { ($0 - 32) * 5.0/9.0 }
.map { "\($0)" }
let fahrenheit = input.celsius
.compactMap { Double($0) }
.map { $0 * 5.0/9.0 + 32 }
.map { "\($0)" }
return TempInOut(fahrenheit: fahrenheit, celsius: celsius)
}
主要要了解的是数据如何从input.fahrenheit流到output.celsius,以及如何从input.celsius流到output.fahrenheit。
这是对您的程序的另一种思考方式……我最近听说“时间设计”的概念,我认为这是一个很好的术语。
这里是将使用上述视图模型的视图控制器。
class ViewController: UIViewController {
@IBOutlet weak var fahrenheitField: UITextField!
@IBOutlet weak var celsiusField: UITextField!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let input = TempInOut(
fahrenheit: fahrenheitField.rx.text.orEmpty.asObservable(),
celsius: celsiusField.rx.text.orEmpty.asObservable()
)
let output = tempViewModel(input: input)
disposeBag.insert(
output.fahrenheit.bind(to: fahrenheitField.rx.text),
output.celsius.bind(to: celsiusField.rx.text)
)
}
}