RxSwift:链接流的问题
Posted
技术标签:
【中文标题】RxSwift:链接流的问题【英文标题】:RxSwift: Issue with chaining streams 【发布时间】:2021-10-23 15:42:06 【问题描述】:我有一个涉及远程服务调用和 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 的任何新值,除非再次触发提交。
【问题讨论】:
【参考方案1】:嗯...显示的代码仅在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)
【讨论】:
【参考方案2】:我认为您过于努力地将事物链接在一起:-)。 让我们分解您的问题,看看是否有帮助。
对您来说重要的事件是按钮按下。当用户按下“提交”按钮时,您想尝试登录。
因此,将主题附加到您的 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
不会在意,直到用户按下提交按钮。
【讨论】:
以上是关于RxSwift:链接流的问题的主要内容,如果未能解决你的问题,请参考以下文章
Swift (RxSwift):使用泛型链接 ViewItem 和 Cell 类