Search code examples
iosswiftcombineweak-referencespublisher

Weak referenced Passthrough Subject doesn't send input


I'm trying some patterns and at some point I realised my passthrough subject doesn't send anything when self is weak. Here what i did :

import Foundation
import Combine

class ViewModel {
    
    var publisher = PassthroughSubject<[WebData], Never>()
    let apis : [ApiProtocol]
        
    init(apis: [ApiProtocol]){
        self.apis = apis
        getData()
    }
        
    func getData(){
        guard !apis.isEmpty else { return }
        apis.forEach {api in
            api.getApiData { [weak self] webData in
                self?.publisher.send(webData)
            }
        }
    }    
}

Since I declare [weak self] and write

self?.publisher.send(webData)

I don't receive any data from my sink closure in the view controller. But if I delete [weak self] and call use send method with a strong self :

    func getData(){
        guard !apis.isEmpty else { return }
        apis.forEach {api in
            api.getApiData { webData in
                self.publisher.send(webData)
            }
        }
    }

It sends data and view controller receives it perfectly. But now, didn't it cause a retain cycle? Why it doesn't send the data when self declared as weak? What am I missing here?

Edit : Here how I use sink closure:


class DataVC: UIViewController {
    
    weak var viewModel : ViewModel?
    var models = [WebData]()
    
    var cancellable = [AnyCancellable]()
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        downloadData()
        tableView.delegate = self
        tableView.dataSource = self
    }
    
    func downloadData() {
        guard let model = viewModel else { return }
        model.publisher
            .receive(on: DispatchQueue.main)
            .sink { _ in

            } receiveValue: { [weak self] value in
                self?.models.append(contentsOf: value)
                DispatchQueue.main.async {
                    self?.tableView.reloadData()
                }
            }.store(in: &cancellable)
    }
}

DataVC table view protovol confirmances are in the extension. They work just fine, so I didn't add them here.

Edit2: Now I change declaration of viewModel in view controller from :

weak var viewModel : ViewModel? 

to not weak one :

var viewModel : ViewModel! 

... and it works with weak referenced publisher at the beginning. Ok, this solved my problem. But, then, why when viewModel is weak, even I (guard-let) unwrap it before use, it doesn't work?


Solution

  • why when viewModel is weak, even I (guard-let) unwrap it before use, it doesn't work?

    It's because weak is weak!

    weak var viewModel : ViewModel?
    

    That means "Don't retain viewModel". Nothing else retains your ViewModel object either, so it just vanishes instantly before you can use it. By the time you say

    guard let model = viewModel
    

    viewModel is gone and has become nil, and we just return.