专注、坚持

Swift 中的 ObservableObject

2022.06.29 by kingcos

Preface

ObservableObject 相比 @State 更自由一些。仅 class 类型可以遵守该协议。视图使用时可以声明为 @ObservedObject,也可使用 @EnvironmentObject 注入。

实现

先来看下 ObservableObject 的部分代码:

/// A type of object with a publisher that emits before the object has changed.
/// 一种带有发布者的对象类型,其在对象更改之前发出。
///
/// By default an ``ObservableObject`` synthesizes an ``ObservableObject/objectWillChange-2oa5v`` publisher that emits the changed value before any of its `@Published` properties changes.
/// 默认情况下,``ObservableObject`` 合成一个 ``ObservableObject/objectWillChange-2oa5v`` 发布者,其在任何 `Published` 属性更改之前发出更改的值。
///
///     class Contact: ObservableObject {
///         @Published var name: String
///         @Published var age: Int
///
///         init(name: String, age: Int) {
///             self.name = name
///             self.age = age
///         }
///
///         func haveBirthday() -> Int {
///             age += 1
///             return age
///         }
///     }
///
///     let john = Contact(name: "John Appleseed", age: 24)
///     cancellable = john.objectWillChange
///         .sink { _ in
///             print("\(john.age) will change")
///     }
///     print(john.haveBirthday())
///     // Prints "24 will change"
///     // Prints "25"
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ObservableObject : AnyObject {

    /// The type of publisher that emits before the object has changed.
    associatedtype ObjectWillChangePublisher : Publisher = ObservableObjectPublisher where Self.ObjectWillChangePublisher.Failure == Never

    /// A publisher that emits before the object has changed.
    var objectWillChange: Self.ObjectWillChangePublisher { get }
}

以其中代码为例:

class Contact: ObservableObject {
    @Published var name: String
    @Published var age: Int // 2. -> objectWillChange

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func haveBirthday() -> Int {
        age += 1   // 1. 改变 age
        return age
    }
}

let john = Contact(name: "John Appleseed", age: 24)
let cancellable = john.objectWillChange
    .sink { _ in
        print("\(john.age) will change") // 3. 输出
}
print(john.haveBirthday()) // 4. 打印结果
// Prints "24 will change"
// Prints "25"

@Published 的本质是通过 willSetobjectWillChange 实现的,也可使用以下方式:

class Contact: ObservableObject {
    let objectWillChange = PassthroughSubject<Void, Never>()

    var age = 0 {
        // 手动在将要设置时调用 send
        willSet { // 2.1 willSet 调用
            print("age - objectWillChange.send")
            // 2.1.1 手动调用 objectWillChange.send
            objectWillChange.send()
        }
    }
    
    var name = "" {
        willSet { // 2.2 willSet 调用
            print("name - objectWillChange.send")
            // 2.2.1 手动调用 objectWillChange.send
            objectWillChange.send()
        }
    }
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func haveBirthday() -> Int {
        age += 1   // 1. 改变 age
        return age
    }
}

let john = Contact(name: "John Appleseed", age: 24)
let cancellable = john.objectWillChange
    .sink { _ in
        print("\(john.age) will change") // 3. 输出
}
print(john.haveBirthday()) // 4. 打印结果
// age - objectWillChange.send
// 24 will change
// 25

参考