SwiftUI - @State属性不更新

5

我在SwiftUI中遇到了一个非常奇怪的问题/bug。在setupSubscription方法中,我正在创建一个订阅subject并将其插入到cancellables集合中。但是,当我打印cancellables的计数时,我得到零。如果我刚刚插入了一个元素,那么为什么集合为空呢?

这可能就是为什么当我点击按钮时handleValue方法没有被调用的原因。以下是控制台的完整输出:

init
begin setupSubscription
setupSubscription subject sink: receive subscription: (CurrentValueSubject)
setupSubscription subject sink: request unlimited
setupSubscription subject sink: receive value: (initial value)
handleValue: 'initial value'
setupSubscription: cancellables.count: 0
setupSubscription subject sink: receive cancel
sent value: 'value 38'
cancellables.count: 0
sent value: 'value 73'
cancellables.count: 0
sent value: 'value 30'
cancellables.count: 0

我做错了什么?为什么我的 subject 订阅被取消了?为什么当我点击按钮时 handleValue 没有被调用?

import SwiftUI
import Combine

struct Test: View {
    
    @State private var cancellables: Set<AnyCancellable> = []
    
    let subject = CurrentValueSubject<String, Never>("initial value")
    
    init() {
        print("init")
        self.setupSubscription()
    }
    
    var body: some View {
        VStack {
            Button(action: {
                let newValue = "value \(Int.random(in: 0...100))"
                self.subject.send(newValue)
                print("sent value: '\(newValue)'")
                print("cancellables.count:", cancellables.count)
            }, label: {
                Text("Tap Me")
            })
        }
    }
    
    func setupSubscription() {
        print("begin setupSubscription")
        
        let cancellable = self.subject
            .print("setupSubscription subject sink")
            .sink(receiveValue: handleValue(_:))
        
        self.cancellables.insert(cancellable)
        
        print("setupSubscription: cancellables.count:", cancellables.count) 
        // prints "setupSubscription: cancellables.count: 0"
    
    }
    
    
    func handleValue(_ value: String) {
        print("handleValue: '\(value)'")
    }
    
    
}
2个回答

7

在这里你做错了几件事情。

永远不要尝试在SwiftUI结构中存储数据。每次视图更改时,它们都会失效并重新加载。这很可能是您的订阅被取消的原因。

对于这样的情况,您应该使用具有已发布属性的ObservableObject或StateObject。当ObservableObjects或StateObjects更改时,包含它们的视图就像使用@State或@Binding一样重新加载:

// ObservedObjects have an implied objectWillChange publisher that causes swiftUI views to reload any time a published property changes. In essence they act like State or Binding variables.
class ViewModel: ObservableObject {
    // Published properties ARE combine publishers
    @Published var subject: String = "initial value"
}

那么在您的观点中:

@ObservedObject var viewModel: ViewModel = ViewModel()

如果确实需要使用发布者,或者在可观察对象属性更改时需要执行某些操作,则无需使用 .sink。这主要用于使用 combine 的 UIKit 应用程序。SwiftUI 具有 .onReceive 视图修饰符,可以完成相同的操作。

以下是我上述建议的实践:

struct Test: View {

    class ViewModel: ObservedObject {
        @Published var subject: String = "initial value"
    }

    @ObservedObject var viewModel: Self.ViewModel

    var body: some View {
        VStack {
            Text("\(viewModel.subject)")

            Button {
                viewModel.subject = "value \(Int.random(in: 0...100))"
            } label: {
                Text("Tap Me")
            }
        }
        .onReceive(viewModel.$subject) { [self] newValue in
            handleValue(newValue)
        }
    }

    func handleValue(_ value: String) {
        print("handleValue: '\(value)'")
    }
}

3

您刚才错误地使用了state - 它与视图相关,在视图呈现时(即在上下文中)才变为可用(已准备好的后端存储)。 在init中还没有状态后端存储,因此您的cancellable就会消失。

这里是可能的工作方法(但我建议将所有主题相关内容移入单独的视图模型中)

在Xcode 12 / iOS 14上进行了测试

struct Test: View {

    private var cancellable: AnyCancellable?
    private let subject = CurrentValueSubject<String, Never>("initial value")

    init() {
        cancellable = self.subject
            .print("setupSubscription subject sink")
            .sink(receiveValue: handleValue(_:))
    }

    var body: some View {
        VStack {
            Button(action: {
                let newValue = "value \(Int.random(in: 0...100))"
                self.subject.send(newValue)
                print("sent value: '\(newValue)'")
            }, label: {
                Text("Tap Me")
            })
        }
    }

    func handleValue(_ value: String) {
        print("handleValue: '\(value)'")
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接