这里的关键是将每个效果视为一个单独的事物并定义导致该效果的原因。
假设您有获取和保存用户数据的方法。 getUserData() -> Observable<UserData>(此 get 将发出一个值然后完成)和 save(_ userData: UserData)。此外,为简单起见,我假设这些不会出错。
那么让我们依次看看每个效果。
- 填写firstName的初始值:
getUserData()
.map(\.firstName)
.bind(to: firstName.rx.text)
.disposed(by: disposeBag)
这很简单。
- 填写姓氏的初始值。这有点难。我们不想订阅两次
getUserData(),因为这会导致两次网络请求...share() 运算符在这里提供帮助:
let userData = getUserData()
.share()
userData
.map(\.firstName)
.bind(to: firstName.rx.text)
.disposed(by: disposeBag)
userData
.map(\.lastName)
.bind(to: lastName.rx.text)
.disposed(by: disposeBag)
或者如果你想稍微压缩一下:
let userData = getUserData()
.share()
disposeBag.insert(
userData.map(\.firstName).bind(to: firstName.rx.text),
userData.map(\.lastName).bind(to: lastName.rx.text)
)
- 邮箱是一样的:
let userData = getUserData()
.share()
disposeBag.insert(
userData.map(\.firstName).bind(to: firstName.rx.text),
userData.map(\.lastName).bind(to: lastName.rx.text),
userData.map(\.email).bind(to: email.rx.text)
)
- 下一个效果,
save 听起来像您遇到的问题。我必须在这里做更多的假设,但我猜你需要发送一个完整的、正确的用户数据对象。如果用户只是更改了他们的名字,您仍然需要在用户数据对象中发送旧的姓氏和电子邮件地址以及新的名字。
那么你从哪里得到原始数据呢?我希望这很明显,上面的userData Observable。此外,您需要收集用户在三个字段中的任何一个字段中输入的任何内容。
这里是转折点... 当您订阅一个文本字段的可观察文本时,它会发出一个空字符串的基值。因此,您需要跳过 Observable 发出的第一个值,并将其替换为 getUserData() 函数的输出。
let latestUserData = Observable.combineLatest(
Observable.merge(firstName.rx.text.orEmpty.skip(1), userData.map(\.firstName)),
Observable.merge(lastName.rx.text.orEmpty.skip(1), userData.map(\.lastName)),
Observable.merge(email.rx.text.orEmpty.skip(1), userData.map(\.email))
) { UserData(firstName: $0, lastName: $1, email: $2) }
上面有很多代码,所以让我们稍微细想一下……对于每个字段,我都会抓取来自getUserData() 的任何内容,并将其与来自相应文本字段的内容合并。我忽略了文本字段中出现的第一个值,因为我知道用户没有输入。
但您只想在用户点击发送按钮时调用保存函数。这就是你的触发器:
sendButton.rx.tap
.withLatestFrom(latestUserData)
.subscribe(onNext: { save($0) })
.disposed(by: disposeBag)
所有代码都在一个地方,包括observe(on:),如果您的网络 getter 在后台线程上发出其值,这是必需的:
override func viewDidLoad() {
super.viewDidLoad()
let userData = getUserData()
.observe(on: MainScheduler.instance)
.share()
let latestUserData = Observable.combineLatest(
Observable.merge(firstName.rx.text.orEmpty.skip(1), userData.map(\.firstName)),
Observable.merge(lastName.rx.text.orEmpty.skip(1), userData.map(\.lastName)),
Observable.merge(email.rx.text.orEmpty.skip(1), userData.map(\.email))
) { UserData(firstName: $0, lastName: $1, email: $2) }
sendButton.rx.tap
.withLatestFrom(latestUserData)
.subscribe(onNext: { save($0) })
.disposed(by: disposeBag)
disposeBag.insert(
userData.map(\.firstName).bind(to: firstName.rx.text),
userData.map(\.lastName).bind(to: lastName.rx.text),
userData.map(\.email).bind(to: email.rx.text)
)
}
如果你想要一个视图模型,那么你就去吧:
func saveViewModel(trigger: Observable<Void>, firstName: Observable<String?>, lastName: Observable<String?>, email: Observable<String?>, initial: Observable<UserData>) -> Observable<UserData> {
let latestUserData = Observable.combineLatest(
Observable.merge(firstName.compactMap { $0 }.skip(1), initial.map(\.firstName)),
Observable.merge(lastName.compactMap { $0 }.skip(1), initial.map(\.lastName)),
Observable.merge(email.compactMap { $0 }.skip(1), initial.map(\.email))
) { UserData(firstName: $0, lastName: $1, email: $2) }
return trigger
.withLatestFrom(latestUserData)
}
您可以使用 RxTest 轻松测试上述内容,而无需从 UIKit 或您的网络堆栈中引入任何内容。将其输出绑定到保存函数,如下所示:
saveViewModel(
trigger: sendButton.rx.tap.asObservable(),
firstName: firstName.rx.text.asObservable(),
lastName: lastName.rx.text.asObservable(),
email: email.rx.text.asObservable(),
initial: userData
)
.subscribe(onNext: { save($0) })
.disposed(by: disposeBag)