在我们的实际开发中,在MVVM
模式下,多数场景是,我们把ViewModel
绑定到UI
控件上,当ViewModel
发生变化时,控件值也跟着改变,而有些时候我们要同时实现当我们改变控件值时,ViewModel
也跟着变化,这个时候就需要双向绑定,接下来我们就逐步实现一下双向绑定;
说明:双向绑定的ViewModel
的属性值一定是基于既是Observable
又是Observer
的
需求描述:有一个文本输入框和一个ViewModel
,当文本内容变化的时候,ViewModel
里面的值跟随着改变,当ViewModel
的值变化的时候,输入框的内容也跟着改变;
首先看ViewModel
里面的内容:
struct ViewModel { let phoneNumber = BehaviorRelay<String?>(value: nil) }
当phoneNumber
变化的时候textField
的text
文本跟着变化:
viewModel.phoneNumber.asObservable().bind(to: textField.rx.text).disposed(by: disposeBag)
当textField
的text
文本变化的时候phoneNumber
跟着变化:
textField.rx.text.asObservable().bind(to: viewModel.phoneNumber).disposed(by: disposeBag)
最后监听当phoneNumber
变化的时候label
的text
文本跟着变化,目的仅为了在UI
层展示:
viewModel.phoneNumber.asObservable().bind(to: label.rx.text).disposed(by: disposeBag)
上述几步我们通过效果可以看到已经可以实现当任意一个变化的时候都会通知到对方,那么我们可否来封装一下上述操作呢?接下来我们采用自定义操作符的方式来把上述操作封装一下:
首先定义一个运算符:
infix operator <---> : DefaultPrecedence
<--->
函数参数一个是用于描述基于UI
控件的ControlProperty
,另一个是既是Observable
又是Observer
的BehaviorRelay
,最后给定一个返回值,类型为Disposables
(此处返回值可有可无、仅为了演示)
func <--->(property: ControlProperty<String?>, relay: BehaviorRelay<String?>) -> Disposable { let disposable = relay.bind(to: property) let disposable2 = property.asObservable().bind(to: relay) return Disposables.create(disposable, disposable2) }
最后来调用一下,并把结果绑定到label
:
let _ = textField.rx.text <---> viewModel.phoneNumber viewModel.phoneNumber.asObservable().bind(to: label.rx.text).disposed(by: disposeBag)
通过运行效果我们可以看到也是满足了我们的需求的;实际RxSwift
自带的Demo
里面也封装好了用于双向绑定的运算符,可以参考GitHub
中对应的Operators.swift
文件,接下来我们看一下它的实现:
func <-> <T>(property: ControlProperty<T>, relay: BehaviorRelay<T>) -> Disposable { if T.self == String.self { #if DEBUG && !os(macOS) fatalError("It is ok to delete this message, but this is here to warn that you are maybe trying to bind to some `rx.text` property directly to relay.\n" + "That will usually work ok, but for some languages that use IME, that simplistic method could cause unexpected issues because it will return intermediate results while text is being inputed.\n" + "REMEDY: Just use `textField <-> relay` instead of `textField.rx.text <-> relay`.\n" + "Find out more here: https://github.com/ReactiveX/RxSwift/issues/649\n" ) #endif } let bindToUIDisposable = relay.bind(to: property) let bindToRelay = property .subscribe(onNext: { n in relay.accept(n) }, onCompleted: { bindToUIDisposable.dispose() }) return Disposables.create(bindToUIDisposable, bindToRelay) }
可以看到它封装的方法,实现双向绑定的原理都是一致的,内部限制了ControlProperty
和BehaviorRelay
的参数必须是String
类型,否则会抛出异常;绑定的时候通过relay
绑定到了property
上,当relay
改变的时候,property
会跟着改变;property
通过订阅的形式,当property
监听到改变的时候,relay
发出信号值也跟着改变;
复制搜一搜分享收藏划线