一、@Published
Combine框架中,@Published发布属性变化的属性包装器
@propertyWrapper
struct Published<Value>
用@Published修饰的属性会自动生成一个publisher
class Weather {
@Published var temperature: Double
init(temperature: Double) {
self.temperature = temperature
}
}
import UIKit
import Combine
class ViewController: UIViewController {
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
let weather = Weather(temperature: 20)
let publisher:Published<Double>.Publisher = weather.$temperature
publisher.sink(receiveValue: { newvalue in
print("新值:\(newvalue)")
let oldvalue = weather.temperature
print("旧值:\(oldvalue)")
}).store(in: &cancellables)
weather.temperature = 25
}
- 用
$
操作符可以直接访问一个publisher,weather。例:weather.$temperature
- 当一个被 @Published 标记的属性即将改变时,系统会在属性的 willSet 阶段触发发布操作。所以此时weather.temperature还是旧值,打印还是20
- @Published 属性包装器是class约束的。只能与class的属性一起使用,而不应用于非类类型,比如结构体。
二、ObservableObject
Combine框架中,对象发生变化之前会发出通知的协议类型
protocol ObservableObject : AnyObject
默认情况下,ObservableObject 会合成一个 objectWillChange 发布者,在其任何 @Published 属性变化之前发出通知。
class Weather:ObservableObject {
@Published var temperature: Double
init(temperature: Double) {
self.temperature = temperature
}
}
import UIKit
import Combine
class ViewController: UIViewController {
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let weather = Weather(temperature: 20)
let publisher:ObservableObjectPublisher = weather.objectWillChange
publisher.sink(receiveValue: { void in
print("属性发生了变化")
}).store(in: &cancellables)
weather.temperature = 25
}
}
- ObservableObject要和 @Published 一起使用,只有被@Published 修饰的属性发生变化时,ObservableObjectPublisher才会发出变化通知
- 订阅者在block回掉时不能输出是哪个属性发生了变化。所以打印输出是void
- 如果属性没有发生变化时,你想强制订阅者回掉block,你可以 手动发出消息
weather.objectWillChange.send()
来触发block,像下面这样
let weather = Weather(temperature: 20)
let publisher:ObservableObjectPublisher = weather.objectWillChange
publisher.sink(receiveValue: { void in
print("属性发生了变化")
}).store(in: &cancellables)
weather.objectWillChange.send()
三、@ObservedObject
SwiftUI框架中,一个属性包装器类型,它订阅了一个可观察对象(observable object),并在可观察对象发生变化时使视图失效(即触发视图更新)。
@frozen
struct ObservedObject<ObjectType> where ObjectType : ObservableObject
在 SwiftUI 中,当你使用 @ObservedObject 修饰一个类对象时,该对象必须遵循 ObservableObject 协议。当这个对象的任何 @Published 属性发生变化时,会自动调用 objectWillChange.send() 方法,从而通知所有订阅了该对象变化的view进行更新。SwiftUI 的运行时接收到这个通知后,会检查相关的视图是否依赖于这些变化的数据。如果是,则这些视图会被标记为“需要更新”,并在下一个渲染周期中重新绘制,以反映最新的数据状态
class Weather:ObservableObject {
@Published var temperature: Double {
didSet{
//当这个对象的任何 @Published 属性发生变化时,会自动调用 objectWillChange.send() 方法,
self.objectWillChange.send()
}
}
init(temperature: Double) {
self.temperature = temperature
}
}
import SwiftUI
struct SwiftUIView: View {
@ObservedObject var weather = Weather(temperature: 11)
Button {
weather.temperature = 36.5
} label: {
Text("\(weather.temperature)")
}.onReceive(weather.objectWillChange) { _ in
print("模型即将发生变化,视图将重新渲染")
}
}
- 当你在一个视图中使用 @ObservedObject 或 @StateObject 来引用一个遵循 ObservableObject 协议的对象时,这个视图就成为了该对象的订阅者。
- 每当可观察对象中的任何 @Published 属性发生变化时,objectWillChange.send() 会被调用,这也会通知所有订阅了该对象的视图。
四、探索数组的变化
class DataManger:ObservableObject{
@Published var dataArray:[Weather] = []
init(dataArray: [Weather]) {
self.dataArray = dataArray
}
}
let manger = DataManger(dataArray: [Weather(temperature: 0),Weather(temperature: 1)])
manger.objectWillChange.sink { void in
print("发生了变化")
}.store(in: &cancellables)
数组的添加、删除肯定是会发生变化的。现在主要的探索的是修改数组某一对象的属性。
4.1、当存储元素是struct时
struct Weather:Identifiable{
let id = UUID() // 每个实例都有唯一的标识符
var temperature: Double
init(temperature: Double) {
self.temperature = temperature
}
}
let manger = DataManger(dataArray: [Weather(temperature: 0),Weather(temperature: 1)])
manger.objectWillChange.sink { void in
print("发生了变化")
}.store(in: &cancellables)
manger.dataArray[0].temperature = 33
运行代码发现发生了变化。是因为struct本身就是值类型,修改属性值会产生新的副本,替换了原来的对象,这样的机制是为了更好的去配合SwiftUI的视图绑定刷新
4.2、当存储元素是class时
class Weather:Identifiable{
let id = UUID() // 每个实例都有唯一的标识符
var temperature: Double = 0
init(temperature: Double) {
self.temperature = temperature
}
}
let manger = DataManger(dataArray: [Weather(temperature: 0),Weather(temperature: 1)])
manger.objectWillChange.sink { void in
print("发生了变化")
}.store(in: &cancellables)
manger.dataArray[1].temperature = 33
运行代码发现没有发生变化,原因是class对象是引用类型,即使修改了其属性值,数组不会发生任何变化。
综上在SwiftUI框架下,我们尽量使用struct作为数据模型比较好,要不然可能不会及时的让view更新。但有时我们的确需要class时,可以这样
class Weather:Identifiable,ObservableObject{
let id = UUID() // 每个实例都有唯一的标识符
@Published var temperature: Double = 0
init(temperature: Double) {
self.temperature = temperature
}
}
import UIKit
import Combine
class DataManger:ObservableObject{
private var cancellables = Set<AnyCancellable>()
@Published var dataArray:[Weather] = []
init(dataArray: [Weather]) {
self.dataArray = dataArray
self.sendObjectWillChange(dataArray: dataArray)
}
func sendObjectWillChange(dataArray: [Weather]){
for (_,obj) in dataArray.enumerated(){
obj.objectWillChange.sink { void in
self.objectWillChange.send()
}.store(in: &cancellables)
}
}
}
当获得元素的属性发生变化时,再手动发送 self.objectWillChange.send(),这样也可以保证view获得及时的更新