RxSwift에서 combineLatest 와 withLatestFrom 차이 이해하기
참고 자료:
https://medium.com/@martinkonicek/rx-combinelatest-vs-withlatestfrom-ccd98cc1cd41
http://adamborek.com/combinelatest-withlatestfrom-zip/
combineLatest
여러 소스 중에서 단 한 가지라도 이벤트를 방출하면, 각각 소스의 맨 마지막 값을 뽑아서 새로운 값을 방출합니다.
disposables += Observable.combineLatest(
view.onNameChanged(), view.onEmailChanged(), ::AccountInfo)
.subscribe { accountInfo ->
view.setNextStepButtonEnabled(accountInfo.isValid())
}
let isPasswordValid = passwordField.rx.text.orEmpty
.map { $0.characters.count >= 8 }
.distinctUntilChanged()
let isEmailVaild = emailField.rx.text.orEmpty
.map(doesEmailMatchRegex)
.distinctUntilChanged()
let isButtonEnabled = Observable.combineLatest(isPasswordValid, isEmailVaild) { $0 && $1 }
isButtonEnabled.bind(to: button.rx.isEnabled)
.disposed(by: disposeBag)
https://rxmarbles.com/#combineLatest
두 이벤트 모두가 변할 때마다 이벤트를 방출할 때 쓰이기에 적합합니다.
이메일과 비밀번호가 변할 때마다 버튼의 enabled 를 계산할 때
withLatestFrom
이메일과 비밀번호 검증에 끝났다고 해 봅시다. 이제 버튼을 누르면 로그인 요청을 보내고 싶습니다.
일단 이렇게 구현할 수 있습니다.
signInButton.rx.tap
.flatMap { [weak self] in
guard let `self` = self else { return .empty() }
let credential = Credential(email: self.emailField.text, password: self.passwordField.text)
return self.loginUseCase.login(using: credential)
}
.subscribe()
.disposed(by: disposeBag)
하지만, 이런 방식은 rx스럽지 않고 weak self를 써야 합니다.
combineLatest
로 버튼 탭, 이메일, 비밀번호 새 옵저블을 합치는 방식을 생각해볼 수 있습니다.
let doLogin = Observable.combineLatest(button.rx.tap, emailField.rx.text.orEmpty, passwordField.rx.text.orEmpty) { ($1, $2) }
doLogin
.map(Credential.init)
.flatMap(loginUseCase.login)
.subscribe()
.disposed(by: disposeBag)
weak self 캡처링을 지웠습니다.
하지만, 이럴 때 combineLatest
을 쓰면 곤란합니다.
combineLatest
은 내부 이벤트중 하나라도 발생할 때마다 새로운 이벤트를 발행합니다. 따라서, 이메일이나 비밀번호를 바꿀 때에도 로그인 이벤트가 발생됩니다.
withLatestFrom
은 한 옵저블을 입력으로 받아서, 다른 옵저버의 맨 마지막 이벤트와 같이 합칩니다.
let credential = Observable.combineLatest(emailField.rx.text.orEmpty, passwordField.rx.text.orEmpty, resultSelector: Credential.init)
button.rx.tap
.withLatestFrom(credential)
.flatMap(loginUseCase.login)
.subscribe()
.disposed(by: disposeBag)
로그인
사용자가 이름을 입력하는 뷰를 제작하고 이름을 검증할 때, 이렇게 사용할 수 있습니다:
disposables += view.onNameFocusChanged() // When focus changes
.withLatestFrom(view.onNameChanged()) // Grab the name
.subscribe { (focus, name) ->
val hasLostFocus = !focus
if (hasLostFocus && !isValidName(name)) {
view.showInvalidName()
} else {
view.clearNameError()
}
}
- 포커스가 변할 때만 이벤트를 방출합니다.
- 사용자가 이름을 입력하지 않고 포커스만 주면, 이벤트를 방출하지 않습니다.
- 포커스를 주지 않고 이름만 어떻게 바뀌어도, 이벤트를 방출하지 않습니다.
https://rxmarbles.com/#withLatestFrom
요약
-
combineLatest
는isEnabled
같은 상태 변수를 계산할 때 적합합니다. withLatestFrom
은 다른 액션을 발동시킬 때 유용합니다.