如何通过另一个可观察对象观察已发布的属性 - Swift Combine

Posted

技术标签:

【中文标题】如何通过另一个可观察对象观察已发布的属性 - Swift Combine【英文标题】:How to observe a published property via another observable 0bject - Swift Combine 【发布时间】:2021-05-19 21:22:27 【问题描述】:

[编辑]

我应该指出,我正在从大量传感器收集数据并且不得不污染视图模型,这就是编排这个,大量的 @Published 和订阅者代码变得非常乏味且容易出错。

我还编辑了代码以更能代表实际问题。

[原创] 在使用发布者时,我试图减少观察另一个类的结果所需的代码量。我宁愿从生成结果的类中发布结果,而不必将其传播回调用类。

这是一个显示问题的简单游乐场示例。

import SwiftUI
import Combine

class AnObservableObject: ObservableObject 
    
    @Published var flag: Bool = false

    
    private var timerPub: Publishers.Autoconnect<Timer.TimerPublisher>
    
    private var timerSub: AnyCancellable?
    
    init() 
        
        timerPub = Timer.publish(every: 1, on: .current, in: .common)
            .autoconnect()
        
        
    
    
    func start() 
        timerSub = timerPub.sink  [self] _ in
            toggleFlag()
        
    
    
    func stop() 
        timerSub?.cancel()
        
    
    
    func toggleFlag() 
        flag.toggle()
    


class AnotherObservableObject: ObservableObject 
    let ao = AnObservableObject()
    
    func start() 
        ao.start()
    
   
    func stop() 
        ao.stop()
    
    


struct MyView: View 
    
    @StateObject var ao = AnotherObservableObject()
    
    var body: some View 
        VStack 
            if ao.ao.flag 
                Image(systemName: "flag").foregroundColor(.green)
            
            HStack 
                Button(action: ao.start(), label: 
                    Text("Toggle Flag")
                )
                Button(action: ao.stop(), label: 
                    Text("Stop")
                )
            
        
        .padding()
    


import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
// Make a UIHostingController
let viewController = UIHostingController(rootView: MyView())
// Assign it to the playground's liveView
PlaygroundPage.current.liveView = viewController
let myView = MyView()

我让它工作的唯一方法就是这样做:

import SwiftUI
import Combine

class AnObservableObject: ObservableObject 
    
    let flag = CurrentValueSubject<Bool, Never>(false)

    private var subscriptions = Set<AnyCancellable>()

    
    private var timerPub: Publishers.Autoconnect<Timer.TimerPublisher>
    
    private var timerSub: AnyCancellable?
    
    init() 
        
        timerPub = Timer.publish(every: 1, on: .current, in: .common)
            .autoconnect()
        
        
    
    
    func start() 
        timerSub = timerPub.sink  [self] _ in
            toggleFlag()
        
    
    
    func stop() 
        timerSub?.cancel()
        
    
    
    func flagPublisher() -> AnyPublisher<Bool, Never> 
        return flag.eraseToAnyPublisher()
    
    
    func toggleFlag() 
        flag.value.toggle()
    


class AnotherObservableObject: ObservableObject 
    let ao = AnObservableObject()
    
    @Published var flag = false
    
    
    
    init() 


        let flagPublisher = ao.flagPublisher()

        flagPublisher
            .receive(on: DispatchQueue.main)
            .assign(to: &$flag)
    
    
    func start() 
        ao.start()
    
   
    func stop() 
        ao.stop()
    
    


struct MyView: View 
    
    @StateObject var ao = AnotherObservableObject()
    
    var body: some View 
        VStack 
            if ao.flag 
                Image(systemName: "flag").foregroundColor(.green)
            
            HStack 
                Button(action: ao.start(), label: 
                    Text("Toggle Flag")
                )
                Button(action: ao.stop(), label: 
                    Text("Stop")
                )
            
        
        .padding()
    


import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
// Make a UIHostingController
let viewController = UIHostingController(rootView: MyView())
// Assign it to the playground's liveView
PlaygroundPage.current.liveView = viewController
let myView = MyView()

想法?

【问题讨论】:

当您需要在视图中直接使用ObservableObject 时,通常需要它。你这里为什么需要一个?如果你没有,你可以只使用结构而不是 AnObservableObject 希望几周后。你现在这样做的方式是链接它们的唯一方法 @loremipsum 谢谢,希望如此!如果 SwitftUI、Combine 和 pro Mac 获得重大更新,应该是一个退出事件! 【参考方案1】:

您不需要ObservableObject ;您可以直接在您的View 中观察Timer

struct MyView: View 
    
    @State private var flag: Bool = false
    
    @State var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View 
        VStack 
            if flag 
                Image(systemName: "flag").foregroundColor(.green)
            
            
            HStack 
                Button(action: start(), label: 
                    Text("Toggle Flag")
                )
                Button(action: stop(), label: 
                    Text("Stop")
                )
            
            .onReceive(timer)  _ in
                flag.toggle()
            
        
        .padding()
    
    
    func start() 
        timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    
    func stop() 
        timer.upstream.connect().cancel()
    

【讨论】:

感谢您的回答,我现在更新了我的代码,并提供了一个希望更好的解释。考虑收集位置数据,对其进行操作,然后将一些结果传递回视图,我想这样做而不必将这些结果传递回 viewModel。这只是我收集的数据点之一。

以上是关于如何通过另一个可观察对象观察已发布的属性 - Swift Combine的主要内容,如果未能解决你的问题,请参考以下文章

如何使剔除可观察数组中的对象属性可观察?

用java语言实现一个观察者模式

如何根据从另一个可观察对象返回的数组中的键创建可观察/ http请求数组

当其他可观察对象为真时,从可观察对象中获取项目

可观察值是另一个可观察值的一部分

React 如何订阅在另一个组件中声明的可观察对象