Cannot get latest value when subscribing to the emitting Publisher

Viewed 179

I have a listener that is registered/deregistered whenever user logs in and out. I have something analogous to the following code,

import Foundation
import Combine

class X : ObservableObject {
    @Published var data: String? = nil
    
    func setData() {
        data = "DATA"
    }
}

let x = X()
var store = Set<AnyCancellable>()
x.$data.sink { v1 in  // I need to add .receive(on:) here
    print(v1)
    if v1 != nil {
        x.$data.sink { v2 in
            print(v2) // Why is this nil
        }.store(in: &store)
    }
}.store(in: &store)

x.setData()

I thought that v2 will be "DATA", but it isn't. I need to do a .receive(on: DispatchQueue.main) in the outer subscription to get them both to receive the "after" state.

My working theory is that the value isn't written after all the handlers are called, by I don't know how to verify it let alone if it's correct.

Why is receive(on:) necessary here?

I know I could've used flatMap, but I didn't use it because I my state is delivered way too late, and I need to unsubscribe to the inner mapped publisher before the sign out code runs to avoid permission issues.

1 Answers

What's happening here is a pretty subtle behavior.

The expression x.$data evaluates to a Published.Publisher. When you subscribe to a Published.Publisher, it publishes its current value immediately, and then each time the Published property receives a new value, the publisher publishes the new value before the new value is stored in the property.

This behavior is documented in the Published reference:

When the property changes, publishing occurs in the property’s willSet block, meaning subscribers receive the new value before it’s actually set on the property.

This means when you call x.setData(), the outer subscription receives the new value ("DATA") in v1, but x.data at that point still evaluates to nil. And when you create the inner subscription to x.$data, it immediately publishes its currently stored value, which is still nil.

Related