Swift 如何在 Protocol 中使用 @Published 属性包裹类型

3,778

本文的知识需要依赖 Combine 框架的相关知识。

对于项目中的 MVVM 架构,通常我们会使用 RX系列来实现。但 Combine 发布以后,我们多了一种选择。通过使用 Combine 中的 @Published 修饰的属性,也可以实现当模型中的数据发生改变时,自动通知 View Controller。

下面先来看一个简单的例子。

简单的例子

// 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: "王二麻子")
let vc = MyVC()
vc.vm = vm

PlaygroundPage.current.liveView = vc

vm.name = "李四"
vm.name = "张三"

简单的说一下上面的代码在做什么:

1、导入依赖的框架

2、定义一个带有 Published 的数据模型

3、在控制器中接受该模型的通知

输出结果:

hello 王二麻子
hello 李四
hello 张三

通过输出结果可以看到代码逻辑正如我们所愿。

但如果我们需要定义一个 Protocol 来进行扩展呢,只要遵守该协议的数据类型就能被 View Controller 监听,那么该如何实现呢?

实现 Protocol 中使用 @Published 属性包裹类型

1、定义 Protocol,通过 Published 将我们的实际类型包裹起来。

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

2、数据类型遵守该协议,将 name 的值返回给 namePublisher。

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

3、先将 vm 的类型修改为 CommonViewModel,然后再修改 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)
    }
}

4、执行代码,代码逻辑执行正常:

hello 王二麻子
hello 李四
hello 张三

5、再添加一种别的 View Model 看是否能通用:

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

stuVM.cls = "二班"
stuVM.cls = "三班"

输出结果:

hello 一班
hello 二班
hello 三班

希望本文能对你带来帮助,😸!!!