Search code examples
swiftswiftuicombinepublisherobservedobject

How to bridge an existing "did change" data model with SwiftUI's ObservableObject?


I have an existing model that looks like this:

struct Data { ... }

struct Parameters { ... }

final class Model {
    let dataDidChange: AnyPublisher<Void, Never> { ... }

    private(set) var data: Data
    var parameters: Parameters {
        didSet { ... }
    }
    
    ...
}

In this model, dataDidChange publisher fires at any time data or parameters did change. Also, parameters may be changed from other models, which will trigger the update.

I am trying to create SwiftUI view, that will mirror the model's state, allowing for representation of data on screen, and changing the parameters.

For this purpose I have created some kind of a view model for view to bind against:

final class ViewModel: ObservableObject {
    private let model: Model

    var parameters: Parameters {
        get { self.model.parameters }
        set { self.model.parameters = newValue }
    }

    var data: Data { self.model.data }

    init(model: Model) {
        self.model = model

        model.dataDidChange
            .sink { self.objectWillChange.send() } // <-- This place
            .store(in: ...)
    }
}

And my view looks like this:

struct MyView: View {
    @ObservedObject var vm: ViewModel

    var body: some View {
        VStack {
            ParametersView(parameters: self.$vm.parameters)
            DataView(self.vm.data)
        }
    }
}

The thing that I don't like about this approach is that objectWillChange publisher is in fact triggered after the data has changed. Although it works, I am wondering, will it be any perfomance/correctness issues with this approach? ObservableObject documentation explicitly states, that

A type of object with a publisher that emits before the object has changed.

What is the preferred way to bridge "did change" style models to SwiftUI?


Solution

  • If you are going to create a view model, then it should do something more interesting than simply rebroadcast information that the view could get from the model itself. Otherwise your view could just be observing the model directly. In this case the view could use the onReceive view modifier watch the model's publisher directly.

    SwiftUI uses the objectWillChange notification to note down that in the next event cycle the view will have to be redrawn. It really doesn't matter if that notification comes before the model changes or after it changes because the effect (redrawing the view) is delayed until the next event cycle. All that really matters is that the data is in place by the time the next event cycle starts and the view is redrawn.