ObservableObject@ObservedObject

  • ObservableObject 是一個 protocol,要求實現協議的類型為 class,有一個需要實作的屬性 objectWillChange
  • 當數據將要發生改變時,這個屬性用來向外進行「廣播」,它的訂閱者(一般是 View 相關的邏輯)在收到通知後,對 View 進行刷新。
  • 創建遵從 ObservableObject 協議的類別後,實際在 View 裡使用需要將此物件宣告為 @ObservedObject

範例:點擊按鈕擲骰子

使用 PassthroughSubjectsend() 方法通知事件將要發生。 PassthroughSubjectCombine 框架中,將於後續介紹。

  • ContentViewmodelreference type,使用 @ObservedObject 將它和 ContentView 關聯起來。當 model 中的屬性 diceState 將要改變,objectWillChange 就會發出事件,使得 body 被調用進行UI刷新。
struct ContentView: View {
    
    @ObservedObject private var model = DiceModel()
    
    var body: some View {
        CounterButton(diceState: $model.diceState)
    }
}

class DiceModel: ObservableObject {
    
    let objectWillChange = PassthroughSubject<Void, Never>()
    
    var diceState: DiceState = .one {
        willSet {
            objectWillChange.send()
        }
    }
}

enum DiceState: Int {
    case one = 1
    case two = 2
    case three = 3
    case four = 4
    case five = 5
    case six = 6
}

@Published

  • ObservableObject 中,每個對介面可能產生影響的屬性都可以如 diceStatewillSet 那樣,手動調用 objectWillChange.send()。若 model 有很多屬性逐一添加 willSet 很麻煩且重複。
  • 簡化寫法為:省略宣告 objectWillChange,並將屬性標記為 Published
class DiceModel: ObservableObject {
    
    @Published var diceState: DiceState = .one
}

@EnvironmentObject

  • 當多個 View 需要訪問到同一個 model,使用 @ObservedObject 需要大量的屬性傳遞,改用 @EnvironmentObject 可以便捷的達到大批量共享。
  • 跟單例很像,只要在 View 層級以上,不論在哪裡都可以訪問到這個環境對象。
struct ContentView: View {
    
    @EnvironmentObject var model: DiceModel
    
    var body: some View {
        CounterButton()
    }
}

struct CounterButton: View {
 
    @EnvironmentObject var model: DiceModel
    
    var body: some View {
        Button {
            let value = Int.random(in: 1...6)
            let dice = DiceState(rawValue: value)
            model.diceState = dice ?? .one
        } label: {
            Circle()
                .frame(width: 150, height: 150, alignment: .center)
                .foregroundColor(.blue)
                .overlay {
                    Text("\(model.diceState.rawValue)")
                        .font(.system(size:72, weight: .bold))
                        .foregroundColor(.white)
                }
        }
    }
}

在創建 ContentView 的地方注入 environmentObject

@main
struct SwiftUIDemoApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(DiceModel())
        }
    }
}

總結

  • @State@Binding 提供 View 內部的狀態儲存,應是簡單的 value type 標記為 private 僅供內部使用。
  • ObservableObject@ObservedObject@EnvironmentObject 則是針對跨越 View 層級的狀態共享,可以處理較複雜的數據類型,為 reference type。需要在數據變化時向外發送通知,來觸發介面刷新。
  • 建議一開始可以先選擇使用 @ObservedObject,若發現狀態可以被限制在同一個 View 層級,則改用 @State;若狀態需要大量共享,則改用 @EnvironmentObject