Home > Software design >  How to re-publish a PassthroughSubject using another publisher in Combine
How to re-publish a PassthroughSubject using another publisher in Combine

Time:11-12

Current (working) situation:

In our app we have several publishers of type PassthroughSubject<Void, Never>. The subscriber of this publisher send out the same type of publisher within the .sink() closure. In a simple playground it would look like that:

//: A UIKit based Playground for presenting user interface

import UIKit
import PlaygroundSupport
import Combine

class MyViewController : UIViewController {

    // MARK: - Observables
    let initialPublisher: PassthroughSubject = PassthroughSubject<Void, Never>()
    let rePublisher = PassthroughSubject<Void, Never>()

    // MARK: - Observer
    private var cancellableSubscriber = Set<AnyCancellable>()

    override func loadView() {
        // MARK: - View Setup
        let view = UIView()
        let button = UIButton(type: .system)
        button.frame = CGRect(x: 100, y: 100, width: 200, height: 20)
        button.setTitle("Button", for: .normal)
        button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
        view.addSubview(button)
        self.view = view

        // MARK: - Subscriptions

        // Event of initial publisher is received and re-published using another subject.
        initialPublisher
            .sink { [weak self] in
                self?.rePublisher.send()
            }
            .store(in: &cancellableSubscriber)

        // The re-published event is received.
        rePublisher
            .sink {
                print("Received!")
            }
            .store(in: &cancellableSubscriber)
    }

    @objc private func buttonAction() {
        self.initialPublisher.send()
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

Prefered (non-working) solution:

Instead of subscribing and re publishing using the .sink() closure and another PassthroughSubject I wanted to re-publish the initial publisher using .receive(subscriber: AnySubscriber) However somehow it doesn't seem to work or maybe I'm understanding the .receive method wrong. I tried the following without luck.

Question:

How can I make the below code work, or is it even the correct way? If not, are there more elegant ways to re-publish than in our code above?

Clarification:

If something is unclear or of you need further examples please leave a comment below and I will try to update my question.

class MyViewController : UIViewController {

    // MARK: - Observables
    let initialPublisher: PassthroughSubject = PassthroughSubject<Void, Never>()
    let rePublisher = PassthroughSubject<Void, Never>()
    var subscriber = AnySubscriber<Void, Never>()

    // MARK: - Observer
    private var cancellableSubscriber = Set<AnyCancellable>()

    override func loadView() {
        // MARK: - View Setup
        let view = UIView()
        let button = UIButton(type: .system)
        button.frame = CGRect(x: 100, y: 100, width: 200, height: 20)
        button.setTitle("Button", for: .normal)
        button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
        view.addSubview(button)
        self.view = view

        // MARK: - Subscriptions

        //Republishing
        subscriber = AnySubscriber(initialPublisher)

        // Event of initial publisher is received and re-published.
        rePublisher.receive(subscriber: subscriber)

        // // The re-published event is received.
        rePublisher
            .sink {
                print("Received!") // <-- does not work!
            }
            .store(in: &cancellableSubscriber)
    }

    @objc private func buttonAction() {
        self.initialPublisher.send()
    }
}

CodePudding user response:

I think you are working too hard. Just pass the AnyPublisher around instead of trying to tie two Subjects together. It doesn't even make sense to try to tie them together because anybody can call send on either of them.

class MyViewController : UIViewController {
    let initialPublisher: PassthroughSubject = PassthroughSubject<Void, Never>()
    var rePublisher: AnyPublisher<Void, Never> {
        initialPublisher.eraseToAnyPublisher()
    }
    private var cancellableSubscriber = Set<AnyCancellable>()

    override func loadView() {
        super.loadView()
        let button: UIButton = {
            let result = UIButton(type: .system)
            result.frame = CGRect(x: 100, y: 100, width: 200, height: 20)
            result.setTitle("Button", for: .normal)
            result.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
            return result
        }()
        view.backgroundColor = .white
        view.addSubview(button)
        
        rePublisher
            .sink {
                print("Received!") // works!
            }
            .store(in: &cancellableSubscriber)
    }

    @objc private func buttonAction() {
        initialPublisher.send()
    }
}
  • Related