LoginSignup
20
6

More than 5 years have passed since last update.

RxSwiftでwithLatestFromが最新じゃなくなることがあるorz

Last updated at Posted at 2017-08-24

Observable①でwithLatestFromをしてObservable②を取り込むとします。
Observable①の上流でObservable②がトリガーだった場合に、想定しいない値が入ってくる場合があります。

想定している最新値が取得できない実装

下記のような実装があるとします。

  1. selectedUserの値が更新され、その値がnilでなかった場合にshowUserでvoidを流す
  2. showUserに値が流れたら、withLatestFromで最新のselectedUserを取得
  3. 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

ソース①では

  1. selectedUser
  2. showUser
  3. withLatestFrom.selectedUser

という順番にsubscribeされています。

subscribe後は

  1. withLatestFrom.selectedUserの直後にwithLatestFrom.selectedUserで初期値のnilをキャッシュ
  2. selectedUserにtatsumiが流れてくる
  3. showUserに値が流れてくる
  4. printを実行
  5. 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

ソース②では

  1. selectedUser
  2. withLatestFromのselectedUser

という順番にsubscribeされています。

subscribe後は

  1. withLatestFrom.selectedUserの直後にwithLatestFrom.selectedUserで初期値のnilをキャッシュ
  2. withLatestFrom.selectedUserにtatsumiが流れてくる
  3. selectedUserにtatsumiが流れてくる
  4. showUserに値が流れてくる
  5. 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

ソース③では

  1. showUser
  2. withLatestFromのselectedUser
  3. selectedUser

という順番にsubscribeされています。

subscribe後は

  1. withLatestFrom.selectedUserのsubscribe直後にwithLatestFrom.selectedUserで初期値のnilをキャッシュ
  2. withLatestFrom.selectedUserにtatsumiが流れてくる
  3. selectedUserにtatsumiが流れてくる
  4. showUserに値が流れてくる
  5. 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

20
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
6