Search code examples
iosswiftcombine

Swift Combine sink called once at setup?


I am setting up a sink like so:

    name.publisher
        .removeDuplicates()
        .receive(on: RunLoop.main)
        .sink { [weak self] config in
            guard let self = self else { return }

            // this closure gets called right away at setup even though the @Published property `name` was already setup and did not change
            
        }.store(in: &subscribers)

The property is declared like so in an observable object:

 @Published var name:String = ""

So, I'm obviously missing something here. Why is sink called once at setup even though name did not change? I can avoid this behavior by using the dropFirst() operator but, I'd like to understand why the closure is always called once immediately after setup?

Why is that?


Solution

  • Here's a playground that uses debugPrint to show you what you get from name.publisher:

    import UIKit
    import Combine
    
    //Holds the score
    class ViewModel : ObservableObject {
        @Published var name = "0"
    }
    
    let viewModel = ViewModel()
    debugPrint(viewModel.name.publisher)
    

    What you get is

    Combine.Publishers.Sequence<Swift.String, Swift.Never>(sequence: "0")

    So you get a sequence publisher that has a single item, "0". It will publish that value once and the sequence will end. Each time a subscriber attaches to that sequence it will get all the items in the sequence (there's only one) and the end.

    This is probably not what you want.

    Instead I think you want to use $name to access the published property:

    import UIKit
    import Combine
    
    //Holds the score
    class ViewModel : ObservableObject {
        @Published var name = "0"
    }
    
    let viewModel = ViewModel()
    let subscription = viewModel.$name.sink { print($0) }
    viewModel.name = "Alex"
    

    When you subscribe to the published property you will still get a posted event that is the current value of the property. However, by using $name you are attaching to a stream that will send you the current value on subscription AND each subsequent value.