Swif学习之@Published、ObservableObject和@ObservableObject

一、@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获得及时的更新

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容