我有一个登录用例,涉及远程服务调用和pin。
在我的视图模型中,我有一个pin的行为继电器,像这样
let pin = BehaviorRelay(value: "")
那么我有这个服务:
protocol LoginService {
func login(pin: String) -> Single<User>
}
同样在视图模型中,我有一个发布中继(支持提交按钮)和一个状态流。初始状态必须设置为.inactive
,一旦提交继电器触发,我需要状态变为.loading
,最终变为.active
。
var state: Observable<State> {
return Observable.merge(
.just(.inactive),
submit.flatMap { [service, pin] in
service.login(pin: pin.value).asObservable().flatMap { user -> Observable<State> in
.just(.active)
}.catch { error in
return .just(.inactive)
}.startWith(.loading)
})
}
问题是,如果pin在提交后发生变化(我的用例涉及在单击提交按钮后清除pin),则使用新的pin值(在这种情况下为空字符串)第二次调用该服务。
我希望这个流只取pin的值,并且只运行一次服务,忽略任何新的pin值,除非再次触发提交。
嗯…所显示的代码仅在submit
发出下一个事件时触发,而不是当pin
发出时触发,因此您有其他代码,您没有显示导致问题的代码,或者您将.next事件不恰当地发送到您的发布中继中。
简而言之,只有当用户点击提交按钮时才发送。next事件,你发布的代码才会正常工作。此外,清除pin文本字段不会改变pin
行为中继,除非您在其他地方做了一些奇怪的事情,所以这不应该是一个问题。
这与您所拥有的基本相同,但使用withLatestFrom
操作符:
class ViewModel {
let submit = PublishRelay<Void>()
let pin = BehaviorRelay(value: "")
let state: Observable<State>
init(service: LoginService) {
self.state = submit
.withLatestFrom(pin)
.flatMapLatest { [service] in
service.login(pin: $0)
.map { _ in State.active }
.catch { _ in .just(.inactive) }
.asObservable()
.startWith(.loading)
}
.startWith(.inactive)
}
}
我不是所有中继的粉丝,我不喜欢你扔掉User对象。我可能会这样做:
class ViewModel {
let service: LoginService
init(service: LoginService) {
self.service = service
}
func bind(pin: Observable<String>, submit: Observable<Void>) -> (state: Observable<State>, user: Observable<User?>) {
let user = submit
.withLatestFrom(pin)
.flatMapLatest { [service] in
service.login(pin: $0)
.map(Optional.some)
.catchAndReturn(nil)
}
.share()
let state = Observable.merge(
submit.map { .loading },
user.map { user in user == nil ? .inactive : .active }
)
.startWith(State.inactive)
return (state: state, user: user)
}
}
我认为你太努力把事情联系在一起了:-)。
让我们把你的问题拆开看看是否有帮助。
对你来说最重要的事情是按下按钮。当用户按下"提交"按钮时
您想尝试登录的按钮。因此,将主题附加到pin输入字段,并让它捕获用户键入的结果。你希望这是一个保存pin的最新值的流:
// bound to a text input field. Has the latest pin entered
var pin = BehaviorSubject(value: "")
然后你可以有一个无限流,当按钮被按下时,它会被发送一个值。当用户按下按钮时,发送的实际值不如它发出的值重要。
var buttonPushes = PublishSubject<Bool>()
在中,我们将创建一个流,在每次按钮被按下时发出一个值。我们将把登录尝试表示为结构体LoginInfo
,其中包含尝试登录所需的所有内容。
struct LoginInfo {
let pin : String
/* Maybe other stuff like a username is needed here */
}
var loginAttempts = buttonPushes.map { _ in
LoginInfo(pin: try pin.value())
}
loginAttempts
看到按钮被按下,并映射到尝试登录。
作为映射的一部分,它从pin
流捕获最新的值,但是loginAttempts
没有直接绑定到pin
流。pin
流可以永远改变,loginAttempts
不会在意,直到用户按下提交按钮。