在SwiftUI中,如何对“视图”的“外​​部”的“ @Published vars”上的更改做出反应

时间:2019-08-22 10:09:23

标签: swiftui

假设我有以下ObservableObject

import SwiftUI

class SomeObservable: ObservableObject {

    @Published var information: String = ""

    init() {
        Timer.scheduledTimer(
            timeInterval: 1.0,
            target: self,
            selector: #selector(updateInformation),
            userInfo: nil,
            repeats: true
        ).fire()
    }

    @objc func updateInformation() {
        information = String("RANDOM_INFO".shuffled().prefix(5))
    }
}

还有一个View,它会观察到:

struct SomeView: View {

    @ObservedObject var observable: SomeObservable

    var body: some View {
        Text(observable.information)
    }
}

以上将按预期工作。
View发生更改时,ObservableObject会重新绘制自身:

viewUpdate

现在要提问

如何在同样观察到struct的“纯” ObservableObject中执行相同的操作(例如调用函数)? “纯”是指不符合View

struct SomeStruct {

    @ObservedObject var observable: SomeObservable

    // How to call this function when "observable" changes?
    func doSomethingWhenObservableChanges() {
        print("Triggered!")
    }
}

(也可以是class,只要它能够对可观察对象的变化做出反应。)

从概念上讲,这似乎很简单,但是我显然缺少了一些东西。

(注意:我正在使用Xcode 11,测试版6。)


更新(供将来的读者使用)

根据@Fabian提供的出色答案,这是一个可能的解决方案:

import SwiftUI
import Combine

class SomeObservable: ObservableObject {

    @Published var information: String = "" // Will be automagically consumed by `Views`.

    let updatePublisher = PassthroughSubject<Void, Never>() // Can be consumed by other classes / objects.

    // Added here only to test the whole thing.
    var someObserverClass: SomeObserverClass?

    init() {
        // Randomly change the information each second.
        Timer.scheduledTimer(
            timeInterval: 1.0,
            target: self,
            selector: #selector(updateInformation),
            userInfo: nil,
            repeats: true
        ).fire()    }

    @objc func updateInformation() {
        // For testing purposes only.
        if someObserverClass == nil { someObserverClass = SomeObserverClass(observable: self) }

        // `Views` will detect this right away.
        information = String("RANDOM_INFO".shuffled().prefix(5))

        // "Manually" sending updates, so other classes / objects can be notified.
        updatePublisher.send()
    }
}

struct SomeObserverView: View {
    @ObservedObject var observable: SomeObservable
    var body: some View {
        Text(observable.information)
    }
}

class SomeObserverClass {

    @ObservedObject var observable: SomeObservable

    // More on AnyCancellable on: apple-reference-documentation://hs-NDfw7su
    var cancellable: AnyCancellable?

    init(observable: SomeObservable) {
        self.observable = observable

        // `sink`: Attaches a subscriber with closure-based behavior.
        cancellable = observable.updatePublisher.sink(receiveValue: { [weak self] _ in
            guard let self = self else { return }
            self.doSomethingWhenObservableChanges()
        })
    }

    func doSomethingWhenObservableChanges() {
        print(observable.information)
    }
}
  

结果

result

(注意:必须运行该应用程序才能检查控制台输出。)

1 个答案:

答案 0 :(得分:5)

旧方法是使用您注册的回调。较新的方法是使用Combine框架创建可以对其进行进一步处理的发布者,或者在这种情况下,sink每次source publisher发送消息时都会被调用。此处的发布者不发送任何内容,因此类型为<Void, Never>

计时器发布者

要通过计时器获取发布者,可以直接通过Combine完成,也可以通过PassthroughSubject<Void, Never>()创建通用发布者,注册消息并通过{{1}在timer-callback中发送消息}。该示例有两种变体。

ObjectWillChange Publisher

每个publisher.send()都有一个ObservableObject发布者,您可以像为.objectWillChange一样向其注册sink。每次您调用它或每次Timer publishers变量更改时都应该调用它。但是请注意,这是在更改之前而不是更改之后被调用的。 (更改完成后,接收器内的@Published会做出反应)。

注册

每个接收器调用都会创建一个DispatchQueue.main.async{},必须将其存储在通常与AnyCancellable应该具有相同生存期的对象中。一旦取消可取消结构(或在其上调用sink),就不会再次调用.cancel()

sink