在SwiftUI中,如何响应“上的更改”@已出版的vars“*a“的*外部;“视图”;

在SwiftUI中,如何响应“上的更改”@已出版的vars“*a“的*外部;“视图”;,swiftui,Swiftui,假设我有以下ObservableObject,它每秒生成一个随机字符串: import SwiftUI class SomeObservable: ObservableObject { @Published var information: String = "" init() { Timer.scheduledTimer( timeInterval: 1.0, target: self,

假设我有以下
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))
    }
}
以及一个
视图
,它观察到:

struct SomeView: View {

    @ObservedObject var observable: SomeObservable

    var body: some View {
        Text(observable.information)
    }
}
上述操作将按预期进行。
视图
可观察对象
更改时会重新绘制自身:

现在回答问题: 我如何在一个“纯”结构中执行同样的操作(比如调用一个函数),该结构也观察相同的
可观察对象
?“纯”指的是不符合
视图的东西:

struct SomeStruct {

    @ObservedObject var observable: SomeObservable

    // How to call this function when "observable" changes?
    func doSomethingWhenObservableChanges() {
        print("Triggered!")
    }
}
(它也可以是
,只要它能够对可观察对象的变化做出反应。)

这在概念上似乎很容易,但我显然遗漏了一些东西

(注意:我使用的是Xcode 11,beta 6。)


更新(面向未来读者)(粘贴在操场上) 以下是一个可能的解决方案,基于@Fabian提供的令人敬畏的答案:

import SwiftUI
import Combine
import PlaygroundSupport

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()
    }
}

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
            .print() // Prints all publishing events.
            .sink(receiveValue: { [weak self] _ in
            guard let self = self else { return }
            self.doSomethingWhenObservableChanges()
        })
    }

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

let observable = SomeObservable()

struct SomeObserverView: View {
    @ObservedObject var observable: SomeObservable
    var body: some View {
        Text(observable.information)
    }
}
PlaygroundPage.current.setLiveView(SomeObserverView(observable: observable))
导入快捷界面
进口联合收割机
导入PlaygroundSupport
类SomeObservable:ObservableObject{
@已发布的变量信息:String=”“//将由“视图”自动使用。
让updatePublisher=PassthroughSubject()//可以被其他类/对象使用。
//添加到这里只是为了测试整个过程。
var someObserverClass:someObserverClass?
init(){
//每秒随机更改信息。
Timer.scheduledTimer(
时间间隔:1.0,
目标:自我,
选择器:#选择器(更新信息),
userInfo:nil,
重复:对
).fire()}
@objc func更新信息(){
//仅用于测试目的。
如果someObserverClass==nil{someObserverClass=someObserverClass(可观察:self)}
//“视图”将立即检测到这一点。
信息=字符串(“随机信息”.shuffled().前缀(5))
//“手动”发送更新,以便通知其他类/对象。
updatePublisher.send()
}
}
类someobserver类{
@观察到的对象var可观察到的:一些可观察到的
//更多关于任何可取消的内容,请访问:apple参考文档mentation://hs-NDfw7su
var可取消:任何可取消?
init(可观察的:一些可观察的){
可观察的
//`sink`:使用基于闭包的行为附加订阅服务器。
可取消=可观察。更新发布器
.print()//打印所有发布事件。
.sink(接收值:{[weak self]\uuIn
guard let self=self-else{return}
self.doSomethinghenobservableChanges()
})
}
func dosomethinghenobservableChanges(){
打印(可观察信息)
}
}
let observable=SomeObservable()
结构SomeObserverView:View{
@观察到的对象var可观察到的:一些可观察到的
var body:一些观点{
文本(可观察信息)
}
}
PlaygroundPage.current.setLiveView(SomeObserverView(可观察:可观察))
结果


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

旧方法是使用您注册的回调。较新的方法是使用
Combine
框架来创建发布服务器,您可以为其注册进一步的处理,或者在本例中是一个
sink
,每次
源发布服务器发送消息时都会调用它。此处的发布者不发送任何内容,因此属于
类型

计时器发布器 要从计时器获取发布服务器,可以直接通过
Combine
或通过
PassthroughSubject()
创建通用发布服务器,注册消息并通过
publisher.send()
计时器回调中发送消息。该示例有两种变体

ObjectWillChange发布者 每个
observeObject
都有一个
.objectWillChange
发布服务器,您可以为其注册一个
接收器
,就像您为
计时器发布服务器
注册一样。每次调用它或每次
@Published
变量更改时都应该调用它。但是请注意,这是在更改之前调用的,而不是在更改之后调用的。(
DispatchQueue.main.async{}
在接收器内部,以在更改完成后作出反应)

登记 每个接收器调用都会创建一个必须存储的
anycancelable
,通常存储在
sink
应该具有相同生存期的对象中。一旦取消构造可取消项(或调用其上的
.cancel()
),就不会再次调用
接收器

import SwiftUI
import Combine

struct ReceiveOutsideView: View {
    #if swift(>=5.3)
        @StateObject var observable: SomeObservable = SomeObservable()
    #else
        @ObservedObject var observable: SomeObservable = SomeObservable()
    #endif

    var body: some View {
        Text(observable.information)
            .onReceive(observable.publisher) {
                print("Updated from Timer.publish")
        }
        .onReceive(observable.updatePublisher) {
            print("Updated from updateInformation()")
        }
    }
}

class SomeObservable: ObservableObject {
    
    @Published var information: String = ""
    
    var publisher: AnyPublisher<Void, Never>! = nil
    
    init() {
        
        publisher = Timer.publish(every: 1.0, on: RunLoop.main, in: .common).autoconnect().map{_ in
            print("Updating information")
            //self.information = String("RANDOM_INFO".shuffled().prefix(5))
        }.eraseToAnyPublisher()
        
        Timer.scheduledTimer(
            timeInterval: 1.0,
            target: self,
            selector: #selector(updateInformation),
            userInfo: nil,
            repeats: true
        ).fire()
    }
    
    let updatePublisher = PassthroughSubject<Void, Never>()
    
    @objc func updateInformation() {
        information = String("RANDOM_INFO".shuffled().prefix(5))
        updatePublisher.send()
    }
}

class SomeClass {
    
    @ObservedObject var observable: SomeObservable
    
    var cancellable: AnyCancellable?
    
    init(observable: SomeObservable) {
        self.observable = observable
        
        cancellable = observable.publisher.sink{ [weak self] in
            guard let self = self else {
                return
            }
            
            self.doSomethingWhenObservableChanges() // Must be a class to access self here.
        }
    }
    
    // How to call this function when "observable" changes?
    func doSomethingWhenObservableChanges() {
        print("Triggered!")
    }
}
导入快捷界面
进口联合收割机
结构接收外部视图:视图{
#如果swift(>=5.3)
@StateObject变量可观测:SomeObservable=SomeObservable()
#否则
@ObservedObject var observable:SomeObservable=SomeObservable()
#恩迪夫
var body:一些观点{
文本(可观察信息)
.onReceive(observable.publisher){
打印(“从Timer.publish更新”)
}
.onReceive(observable.updatePublisher){
打印(“从updateInformation()更新”)
}
}
}
类SomeObservable:ObservableObject{
@已发布的变量信息:String=“”
var publisher:AnyPublisher!=nil
init(){
publisher=Timer.publish(every:1.0,on:RunLoop.main,in:.common).autoconnect().map{in
打印(“更新信息”)
//self.information=String(“RANDOM_INFO”.shuffled()。前缀(5))
}.删除任何发布者()
Timer.scheduledTimer(
时间间隔:1.0,
目标:自我,
选择器:#选择器(更新信息),
userInfo:nil,
重复:对
).火灾
}
让updatePublisher=PassthroughSubject()
@objc func更新信息(){
英富曼集团