Home > Enterprise >  Combine why publish in init or not in init
Combine why publish in init or not in init

Time:08-02

I've been writing Swift with Combine for a while now. I've even finished an app that I use privately. I've been struggling to understand and use Combine effectively. Lately I'm working through the books "Using Combine with Swift" by Joseph Heck and "Combine Mastery" from Big Mountain by Mark Moeykens. Both books have been extremely helpful in understanding Combine.

BUT..... I'm struggling to understand why in the examples I see code where the publisher / subscriber code is placed within init() {} like this example from Combine Mastery :


//  Copyright © 2021 Mark Moeykens. All rights reserved. | @BigMtnStudio
class Published_IntroductionViewModel: ObservableObject {
    var characterLimit = 30
    @Published var data = ""
    @Published var characterCount = 0
    @Published var countColor = Color.gray

    init() {
        $data
            .map { data -> Int in
                return data.count
            }
            .assign(to: &$characterCount)
        
        $characterCount
            .map { [unowned self] count -> Color in
                let eightyPercent = Int(Double(characterLimit) * 0.8)
                if (eightyPercent...characterLimit).contains(count) {
                    return Color.yellow
                } else if count > characterLimit {
                    return Color.red
                }
                return Color.gray
            }
            .assign(to: &$countColor)
    }
}

and for other examples the code is not in init() {} like this from Combine Mastery:


//  Copyright © 2021 Mark Moeykens. All rights reserved. | @BigMtnStudio

class Just_IntroductionViewModel: ObservableObject {
    @Published var data = ""
    @Published var dataToView: [String] = []
    
    func fetch() {
        let dataIn = ["Julian", "Meredith", "Luan", "Daniel", "Marina"]
        
        _ = dataIn.publisher
            .sink { [unowned self] (item) in
                dataToView.append(item)
            }
        
        if dataIn.count > 0 {
            Just(dataIn[Int.random(in: 0..<5)])
                .map { item in
                    item.uppercased()
                }
                .assign(to: &$data)
        }
    }
}

The answer may be "Duh that's so obvious" but I'm afraid it completely escapes me and would appreciate any guidance on this topic.

CodePudding user response:

It really depends on the actual circumstances of the Combine pipeline that you are setting up where that setup needs to be called from.

In the first example, some @Published properties depend on other @Published properties, so whenever the value of the property changes, all properties that depend on this property needs to be updated as well. For this to happen automatically, the Combine pipeline needs to be configured in init, otherwise you'd need to ensure that a specific setup method is called after an instance of your type is created, which isn't ideal.

On the other hand, when you're doing computationally expensive or data intensive tasks, such as network requests every time an @Published property changes, then you might not want to automatically start this pipeline in the init, you might only want to start it whenever the specific view that displays this data is on the screen. From the simplified example, it seems this is what the 2nd example in your question is trying to showcase.

Moreover, the 2nd example actually sets up a Combine pipeline where the upstream Publisher is created inside the method, rather than it being an @Published property of the type. So creating the pipeline in a fetch method ensures that you can re-execute fetch several times when necessary, not just once on initialisation.

CodePudding user response:

Within the world of Reactive Programming there is a concept of publishers for events that happen over a long time that subscribers might be interested in. Subscribers only receive the items delivered to them after they subscribe. Any earlier events are not seen.

There is also the concept of publishers which repeat the same set of events every time a subscriber attaches to them.

In the first case, where the subscriptions go into init, the observed object is going have its values changing throughout its entire lifetime. The publishers are there to let you know when those changes happen. Subscribers don't know where on the timeline the values will change, and they only receive events that happen after they have subscribed to the publishers that expose those changes. Since they want to know about all the events they need to subscribe as early as possible... in the constructor.

In the second case, the publisher of the dataIn array publishes all the items in the array, all at once, and each and every time a new subscriber attaches. This publisher is not exposing a long running set of events to subscribers. It's repeating the same set of information for each subscriber. The object wants those items re-published every time fetch is called. So the work is done in the fetch method.

These are sometimes called "Hot" and "Cold" publishers. A Hot publisher is something which is 'tuning in' to a series of events that is already in-progress (say a publisher which is publishing the ticks of a clock, or a series of mouse events - things that would happen whether the publisher was there to tell someone about it or not). A "Cold" publisher is one that doesn't generate any events until a subscriber attaches to it. This is like the array publishers in your second example.

Personally I've always been confused by the terms "hot" and "cold" and I often have to look them up when I encounter them. YMMV.

  • Related