skip to Main Content

I keep the Model as a Published var in the ViewModel and Observe it from the View.

When the model process goes into a background thread, if you publish the model value, the Xcode thread checker will react.

Publishing changes from background threads is not allowed; make sure to publish values ​​from the main thread (via operators like receive (on :)) on model updates.

Is issued.

@StateObject var viewModel = ViewModel()
class ViewModel: ObservableObject {

    @Published var model = Model()
    var thisValue:String {
        return model.thisValue // I want to use this value in view
    }
struct Model {
    var thisValue:String = "value" // I want to change this value on background threads.

I’d like to know how to receive the model value in the main thread, but
I didn’t quite understand and asked a question.

I would be very happy if you could tell me.

2

Answers


  1. Don’t quite get what you are trying to do. But if you are not happy with DispatchQueue.main.async you may try one of the following options:

    Combine

    struct Model {
        var thisValue: String = "value"
    }
    
    class ViewModel: ObservableObject {
        @Published var model = Model()
        var thisValue: String {
            model.thisValue
        }
        func setThisValueInBackground() {
            DispatchQueue.global(qos: .background).async {
                Just(Model(thisValue: "newValue")).receive(on: RunLoop.main).assign(to: &self.$model)
            }
        }
    }
    

    Unpublish your model and use property observer on it instead

    class ViewModelV2: ObservableObject {
        var model = Model() {
            didSet {
                thisValue = model.thisValue
            }
        }
        @Published private(set) var thisValue: String = ""
        func setThisValueInBackground() {
            DispatchQueue.global(qos: .background).async {
                self.model = Model(thisValue: "newValue")
            }
        }
    }
    
    Login or Signup to reply.
  2. Like Matt mentioned above, you want to "subscribe" to the model value using .receive(on:) and .sink

    Though I’m not sure how @StateObject comes into play, or if you need it. The basic format I’ve seen used is:

    class ViewModel {
        // this could be a computed property or...
        @Published var thisValue: SomeType
        // ...more viewModel logic that potentially changes the value of `thisValue`
    }
    
    class View: UIView {
        var viewModel: ViewModel
        var cancellables = Set<AnyCancellable>()
    
        // in viewDidLoad start your subscription to the viewModel's published property
        override func viewDidLoad() {
            super.viewDidLoad()
            viewModel.$thisValue
                .receive(on: DispatchQueue.main)
                .sink { [weak self] result in
                    // do something with the result
                }
                .store(in: $cancellables)
        }
    }
    

    Also, if the result is something really simple like a String and all you want to do is set a labels text property, instead of using .sink you can use .assign

    viewModel.$thisValue
        .receive(on: DispatchQueue.main)
        .assign(to: .text, on: label)
        .store(in: $cancellables)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search