Observable①でwithLatestFromをしてObservable②を取り込むとします。
Observable①の上流でObservable②がトリガーだった場合に、想定しいない値が入ってくる場合があります。
想定している最新値が取得できない実装
下記のような実装があるとします。
- selectedUserの値が更新され、その値がnilでなかった場合にshowUserでvoidを流す
- showUserに値が流れたら、withLatestFromで最新のselectedUserを取得
- withLatetFromで取得したselectedUserをprint
let selectedUser = Variable<String?>(nil)
let showUser = PublishSubject<Void>()
_ = selectedUser.asObservable()
.filter { $0 != nil }
.debug("selectedUser")
.subscribe(onNext: { _ in
showUser.onNext()
})
_ = showUser
.withLatestFrom(selectedUser.asObservable().debug("withLatestFrom.selectedUser"))
.debug("showUser")
.subscribe(onNext: { user in
print("print user = ", user)
})
print("set tatusmi")
selectedUser.value = "tatsumi"
さて、上記の実装の場合、どのようなものがprintで表示されるでしょうか。
showUserのトリガーがnilではないselectedUserになっているので、print user = tatsumi
になることを想定しています。
しかしながら、実際にはprint user = nilとなります。
showUserのトリガーがselectedUserに値が流れてnil出ないことを確認しているにも関わらず、withLatestFromで取得したselectedUserがnilになってしまっています。
デバッグログは下記のようになります。
2017-08-24 14:51:32.339: selectedUser -> subscribed
2017-08-24 14:51:32.342: showUser -> subscribed
2017-08-24 14:51:32.342: withLatestFrom.selectedUser -> subscribed
2017-08-24 14:51:32.342: withLatestFrom.selectedUser -> Event next(nil)
set tatusmi
2017-08-24 14:51:32.343: selectedUser -> Event next(Optional("tatsumi"))
2017-08-24 14:51:32.343: showUser -> Event next(nil)
print user = nil
2017-08-24 14:51:32.343: withLatestFrom.selectedUser -> Event next(Optional("tatsumi"))
2017-08-24 14:51:32.344: selectedUser -> Event completed
2017-08-24 14:51:32.344: selectedUser -> isDisposed
2017-08-24 14:51:32.344: withLatestFrom.selectedUser -> Event completed
2017-08-24 14:51:32.344: withLatestFrom.selectedUser -> isDisposed
ソース①では
- selectedUser
- showUser
- withLatestFrom.selectedUser
という順番にsubscribeされています。
subscribe後は
- withLatestFrom.selectedUserの直後にwithLatestFrom.selectedUserで初期値のnilをキャッシュ
- selectedUserにtatsumiが流れてくる
- showUserに値が流れてくる
- printを実行
- withLatestFrom.selectedUserにtatsumiが流れてくる
という流れで実行されます。
想定している最新値が取得できる実装①
それでは、showUserを間に挟まない実装をした場合はどうでしょうか。
let selectedUser = Variable<String?>(nil)
_ = selectedUser.asObservable()
.withLatestFrom(selectedUser.asObservable().debug("withLatestFrom.selectedUser"))
.filter { $0 != nil }
.debug("selectedUser")
.subscribe(onNext: { user in
print("print user = ", user)
})
print("set tatusmi")
selectedUser.value = "tatsumi"
実行結果はprint user = tatsumiとなります。
2017-08-24 14:52:02.103: selectedUser -> subscribed
2017-08-24 14:52:02.103: withLatestFrom.selectedUser -> subscribed
2017-08-24 14:52:02.104: withLatestFrom.selectedUser -> Event next(nil)
set tatusmi
2017-08-24 14:52:02.105: withLatestFrom.selectedUser -> Event next(Optional("tatsumi"))
2017-08-24 14:52:02.105: selectedUser -> Event next(Optional("tatsumi"))
print user = Optional("tatsumi")
2017-08-24 14:52:02.106: withLatestFrom.selectedUser -> Event completed
2017-08-24 14:52:02.106: withLatestFrom.selectedUser -> isDisposed
2017-08-24 14:52:02.106: selectedUser -> Event completed
2017-08-24 14:52:02.107: selectedUser -> isDisposed
ソース②では
- selectedUser
- withLatestFromのselectedUser
という順番にsubscribeされています。
subscribe後は
- withLatestFrom.selectedUserの直後にwithLatestFrom.selectedUserで初期値のnilをキャッシュ
- withLatestFrom.selectedUserにtatsumiが流れてくる
- selectedUserにtatsumiが流れてくる
- showUserに値が流れてくる
- printを実行
という流れで実行されます。
想定している最新値が取得できる実装②
let selectedUser = Variable<String?>(nil)
let showUser = PublishSubject<Void>()
_ = showUser
.withLatestFrom(selectedUser.asObservable().debug("withLatestFrom.selectedUser"))
.debug("showUser")
.subscribe(onNext: { user in
print("print user = ", user)
})
_ = selectedUser.asObservable()
.filter { $0 != nil }
.debug("selectedUser")
.subscribe(onNext: { _ in
showUser.onNext()
})
print("set tatusmi")
selectedUser.value = "tatsumi"
実行結果はprint user = tatsumiとなります。
2017-08-24 14:50:42.238: showUser -> subscribed
2017-08-24 14:50:42.239: withLatestFrom.selectedUser -> subscribed
2017-08-24 14:50:42.239: withLatestFrom.selectedUser -> Event next(nil)
2017-08-24 14:50:42.241: selectedUser -> subscribed
set tatusmi
2017-08-24 14:50:42.242: withLatestFrom.selectedUser -> Event next(Optional("tatsumi"))
2017-08-24 14:50:42.242: selectedUser -> Event next(Optional("tatsumi"))
2017-08-24 14:50:42.242: showUser -> Event next(Optional("tatsumi"))
print user = Optional("tatsumi")
2017-08-24 14:50:42.243: withLatestFrom.selectedUser -> Event completed
2017-08-24 14:50:42.243: withLatestFrom.selectedUser -> isDisposed
2017-08-24 14:50:42.243: selectedUser -> Event completed
2017-08-24 14:50:42.243: selectedUser -> isDisposed
ソース③では
- showUser
- withLatestFromのselectedUser
- selectedUser
という順番にsubscribeされています。
subscribe後は
- withLatestFrom.selectedUserのsubscribe直後にwithLatestFrom.selectedUserで初期値のnilをキャッシュ
- withLatestFrom.selectedUserにtatsumiが流れてくる
- selectedUserにtatsumiが流れてくる
- showUserに値が流れてくる
- printを実行
という流れで実行されます。
その他の実装
withLatestFromではなく、flatMapでselectedUserを都度取り込みます。
let selectedUser = Variable<String?>(nil)
let showUser = PublishSubject<Void>()
_ = selectedUser.asObservable()
.filter { $0 != nil }
.debug("selectedUser")
.subscribe(onNext: { _ in
showUser.onNext()
})
_ = showUser
.flatMap { selectedUser.asObservable().debug("withLatestFrom.selectedUser") }
.debug("showUser")
.subscribe(onNext: { user in
print("print user = ", user)
})
print("set tatusmi")
selectedUser.value = "tatsumi"
2017-08-24 15:16:57.120: selectedUser -> subscribed
2017-08-24 15:16:57.122: showUser -> subscribed
set tatusmi
2017-08-24 15:16:57.123: selectedUser -> Event next(Optional("tatsumi"))
2017-08-24 15:16:57.124: withLatestFrom.selectedUser -> subscribed
2017-08-24 15:16:57.124: withLatestFrom.selectedUser -> Event next(Optional("tatsumi"))
2017-08-24 15:16:57.124: showUser -> Event next(Optional("tatsumi"))
print user = Optional("tatsumi")
最後に
RxSwiftのissueにも似たようなものがありました。
withLatestFrom doesn't actually provide latest value #1121