2nebin
2nebin

Reputation: 164

How can I wait until all Combine publishers finished their jobs in Swift?

I'd like to be notified the moment all Combine publishers have done their work like DispatchGroup and .notify do.

For example, at the below codes, I want to show ProgressView while publishers(pub1, pub2) doing their job.

import Combine
import Foundation
import SwiftUI

struct SwiftUIView: View {
    @State var isFinished = false
    let pub1 = ["one", "two", "three", "four"].publisher
    let pub2 = ["five", "six", "seven", "eight"].publisher
    var subscriptions = Set<AnyCancellable>()

    var body: some View {
        if isFinished {
            Text("Hello, World!")
        } else {
            ProgressView()
        }
    }
    
    init() {
        pub1
            .sink { print($0) }
            .store(in: &subscriptions)
        pub2
            .sink { print($0) }
            .store(in: &subscriptions)
        
//         Where should I write this code?
//         isFinished = true
    }
}

My question is that how can I wait until publishers finish and show "Hello world" at the right time?

Is there anything I should know? If so, please let me know. Thank you!

Upvotes: 1

Views: 5095

Answers (2)

ahmad mohammadi
ahmad mohammadi

Reputation: 46

You can use the Zip operator. Zip operator only publishes after receiving events from all publishers. On the other hand, Merge will publish every time one of the publishers, publishes new value.

class ViewModel : ObservableObject {
    
    @Published var isFinished = false
    let pub1 = ["one", "two", "three", "four"].publisher
    let pub2 = ["five", "six", "seven", "eight"].publisher
    
    private var cancelable = Set<AnyCancellable>()
    
    init() {
        pub1.zip(pub2)
            .sink(receiveCompletion: { _ in
                self.isFinished = true
            }, receiveValue: {
                print("\($0),\($1)")
            })
            .store(in: &cancelable)
    }
}

struct SwiftUIView: View {
    @StateObject private var model = ViewModel()
    var body: some View {
        if model.isFinished {
            Text("Hello, World!")
        } else {
            ProgressView()
        }
    }
}

Upvotes: 2

vadian
vadian

Reputation: 285064

A possible way is a view model. In this class merge the publishers and use the receiveCompletion: parameter

class ViewModel : ObservableObject {
    
    @Published var isFinished = false
    let pub1 = ["one", "two", "three", "four"].publisher
    let pub2 = ["five", "six", "seven", "eight"].publisher
    
    private var subscriptions = Set<AnyCancellable>()
    
    init() {
        pub1
            .sink { print($0) }
            .store(in: &subscriptions)
        pub2
            .sink { print($0) }
            .store(in: &subscriptions)
        
        pub1.merge(with: pub2)
            .sink(receiveCompletion: { _ in
                self.isFinished = true
            }, receiveValue: { _ in  })
            .store(in: &subscriptions)
    }
}

struct SwiftUIView: View {
    @StateObject private var model = ViewModel()
    var body: some View {
        if model.isFinished {
            Text("Hello, World!")
        } else {
            ProgressView()
        }
    }
}

Upvotes: 1

Related Questions