2

I have always placed the DisposeBag in ViewController in MVVM with RxSwift like it said in this topic:

On iOS, for the DisposeBag in MVVM, can it be placed in ViewModel?

But with combine, since the View is a struct and cancelable can't be placed in this, I am stuck with solution.

How to manage subscription between View and VM in Combine without add cancelable in ViewModel

Or maybe, in SwiftUI / Combine, there is no choice to place cancelables in VM.

There is an example of implementation in SiwftUI / Combine :

The ViewModel


class EquityViewModel: ObservableObject {
    
    @Injected private var api: AlphaVantageAPI
    
    private var cancellables = Set<AnyCancellable>()
    private let code: String
    @Published private var result: Quote?
    @Published var price: String = ""
    
    init(code: String) {
        self.code = code
        self.$result
            .map {
                return "\($0?.price ?? 0) €"
            }.assign(to: &$price)
    }
    
    
    func addToPortfolio(){
        
    }
    
    func onAppear() {
        self.api.quote(symbol: self.code).share()
            .sink { completion in }
                receiveValue: { quote in
                    self.result = quote.quote
                }
            .store(in: &cancellables)
    }
    
}

The View

struct EquityView: View {
    
    @ObservedObject  var viewModel: EquityViewModel
    
    init(viewModel: EquityViewModel) {
        self.viewModel = viewModel
    }
    
    var body: some View {
        ZStack {
            Color("primary").edgesIgnoringSafeArea(.all)
            VStack {
                Text("Stock Price")
                    .foregroundColor(.white)
                    .frame(minWidth: 0,
                           maxWidth: .infinity,
                           alignment: .topLeading)
                    .padding()
                HStack {
                    Text(self.viewModel.price)
                        .foregroundColor(.white)
                    Text("+4.75 %")
                        .foregroundColor(.white)
                        .padding(.leading, 20)
                }.frame(minWidth: 0,
                        maxWidth: .infinity,
                        alignment: .topLeading)
                .padding()
                Button(action: self.viewModel.addToPortfolio, label: {
                    Text("Add to portfolio")
                        .foregroundColor(.white)
                        .frame(minWidth: 0,
                                maxWidth: .infinity,
                                maxHeight: 30,
                                alignment: .center)
                        .background(Color.blue)
                        .cornerRadius(5)
                }).padding()
                Spacer()
            }
        }.frame(alignment: .leading)
        .onAppear(perform: self.viewModel.onAppear)
    }
}
Kevin ABRIOUX
  • 16,507
  • 12
  • 93
  • 99
  • You have to store them somewhere, so what's bad to keep it close to creator? Of course you have to avoid reference cycling, but you have to avoid it always. Btw, use `sink` instead of `assign` for that, and don't forget `[weak self]` in corresponding closures. – Asperi Sep 11 '20 at 15:39
  • Whoever subscribes should own the subscription. In your example, `EquityViewModel` properties change in response to published values, so it should own it. Alternatively, you could provide the publishers themselves and use `onreceive(_:perform:)` to subscribe and change some state variables – New Dev Sep 11 '20 at 20:31

2 Answers2

0

It that what you search? I can't test it i don't know what a library you are use

class EquityViewMode: ObservableObject {
//    @Injected private var api: AlphaVantageAPI
    
    var pricePublisher: AnyPublisher<String, Never>
    @Published var price: String = ""

    init(){
        
//         init your publisher like
//        pricePublisher = self.api.quote(symbol: self.code)
//            .share()
//            .map { "\($0?.price ?? 0) €" }
//            .eraseToAnyPublisher()

}


struct EquityView: View {

    @ObservedObject var viewModel: EquityViewModel
    var handle: AnyCancellable? = nil

    init(m:EquityViewMode) {
        viewModel = m
        handle = m.pricePublisher.assign(to: \.price, on: self.viewModel)
    }

    var body: some View{
        Text(viewModel.price)
    }
}
janhendry
  • 1
  • 1
0

You have to store them in some place (generally in the viewModel) to have the reference and delete them when you don't need them anymore. To do it you can use the deinit method:

deinit {
    cancellables.removeAll()
}