Knowledge in this article depends on knowledge of the Combine framework.

For MVVM architectures in projects, we usually use the RX family to implement them. But with the Combine, we have an option. It is also possible to automatically notify the View Controller when data in the model changes by using the @Published modified property in Combine.

Let’s start with a simple example.

Simple example

// 1.
import Combine
import PlaygroundSupport

// 2.
class MyViewModel {
    @Published var name: String
    
    init(name: String) {
        self.name = name
    }
}

class MyVC: UIViewController {
    var vm: MyViewModel!
    private var cancellables: Set<AnyCancellable> = []
    override func viewDidLoad() {
        super.viewDidLoad()
        // 3. 
        vm.$name
            .receive(on: RunLoop.main)
            .sink { (name) in
                print("hello \(name)")
        }.store(in: &cancellables)
    }
}

let vm = MyViewModel(name: "Two pockmarks of Wang.")
let vc = MyVC()
vc.vm = vm

PlaygroundPage.current.liveView = vc

vm.name = "Bill"
vm.name = "Zhang"
Copy the code

A brief description of what the above code does:

1. Import dependent frameworks

2. Define a Published data model

3. Accept notifications from the model in the controller

Output result:

Hello wang Two pockmarked zi hello Li Four hello Zhang ThreeCopy the code

The output shows that the code logic is exactly what we want it to be.

But what if we need to define a Protocol to extend it so that the View Controller can listen as long as the data type of the Protocol is followed?

Implement Protocol using the @Published attribute package type

Define Protocol to wrap our actual type with Published.

protocol CommonViewModel {
    var namePublisher: Published<String>.Publisher { get }
}
Copy the code

2. The data type complies with the protocol and returns the value of name to namePublisher.

class MyViewModel: CommonViewModel {
    @Published var name: String
    var namePublisher: Published<String>.Publisher { $name }
    
    init(name: String) {
        self.name = name
    }
}
Copy the code

3. Change the VM type to CommonViewModel and then modify the accept object in View Controller.

class MyVC: UIViewController {
    var vm: CommonViewModel!
    private var cancellables: Set<AnyCancellable> = []
    override func viewDidLoad() {
        super.viewDidLoad()

        vm.namePublisher
            .receive(on: RunLoop.main)
            .sink { (name) in
                print("hello \(name)")
        }.store(in: &cancellables)
    }
}
Copy the code

4. Code execution and normal code logic execution:

Hello wang Two pockmarked zi hello Li Four hello Zhang ThreeCopy the code

5. Add another View Model to see if it can be used:

class StuViewModel: CommonViewModel {
    @Published var cls: String
    var namePublisher: Published<String>.Publisher { $cls }
    init(cls: String) {
        self.cls = cls
    }
}
let stuVM = StuViewModel(cls: "Class")
vc.vm = stuVM
PlaygroundPage.current.liveView = vc

stuVM.cls = "Class 2"
stuVM.cls = "Three"
Copy the code

Output result:

Hello class one hello class two Hello class threeCopy the code

Hope this article can help you, 😸!!