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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容