使用 SwiftUI/Combine,如何避免在 ViewModel 中放置可取消项

Posted

技术标签:

【中文标题】使用 SwiftUI/Combine,如何避免在 ViewModel 中放置可取消项【英文标题】:With SwiftUI / Combine, How to avoid to put cancellables in ViewModel 【发布时间】:2020-09-11 15:33:02 【问题描述】:

我一直将 DisposeBag 放在带有 RxSwift 的 MVVM 中的 ViewController 中,就像它在本主题中所说的那样:

On ios, for the DisposeBag in MVVM, can it be placed in ViewModel?

但是使用 combine,由于 View 是一个结构并且可取消的不能放置在其中,所以我被困在了解决方案中。

如何在Combine中管理View和VM之间的订阅而不在ViewModel中添加可取消的

或者,在 SwiftUI/Combine 中,没有选择在 VM 中放置可取消项。

在 SiwftUI/Combine 中有一个实现示例:

视图模型


class EquityViewModel: ObservableObject 
    
    @Injected private var api: AlphaVantageAPI
    
    private var cancellables = Set<AnyCancellable>()
    private let code: String
    @Published private var result: Quote?
    @Published var price: String = ""
    
    init(code: String) 
        self.code = code
        self.$result
            .map 
                return "\($0?.price ?? 0) €"
            .assign(to: &$price)
    
    
    
    func addToPortfolio()
        
    
    
    func onAppear() 
        self.api.quote(symbol: self.code).share()
            .sink  completion in 
                receiveValue:  quote in
                    self.result = quote.quote
                
            .store(in: &cancellables)
    
    

观点

struct EquityView: View 
    
    @ObservedObject  var viewModel: EquityViewModel
    
    init(viewModel: EquityViewModel) 
        self.viewModel = viewModel
    
    
    var body: some View 
        ZStack 
            Color("primary").edgesIgnoringSafeArea(.all)
            VStack 
                Text("Stock Price")
                    .foregroundColor(.white)
                    .frame(minWidth: 0,
                           maxWidth: .infinity,
                           alignment: .topLeading)
                    .padding()
                HStack 
                    Text(self.viewModel.price)
                        .foregroundColor(.white)
                    Text("+4.75 %")
                        .foregroundColor(.white)
                        .padding(.leading, 20)
                .frame(minWidth: 0,
                        maxWidth: .infinity,
                        alignment: .topLeading)
                .padding()
                Button(action: self.viewModel.addToPortfolio, label: 
                    Text("Add to portfolio")
                        .foregroundColor(.white)
                        .frame(minWidth: 0,
                                maxWidth: .infinity,
                                maxHeight: 30,
                                alignment: .center)
                        .background(Color.blue)
                        .cornerRadius(5)
                ).padding()
                Spacer()
            
        .frame(alignment: .leading)
        .onAppear(perform: self.viewModel.onAppear)
    

【问题讨论】:

您必须将它们存储在某个地方,那么将其保存在创建者附近有什么不好呢?当然,您必须避免参考循环,但您必须始终避免它。顺便说一句,使用sink 而不是assign,并且不要忘记相应闭包中的[weak self] 订阅者应该拥有订阅的所有权。在您的示例中,EquityViewModel 属性会根据发布的值而更改,因此它应该拥有它。或者,您可以自己提供发布者并使用onreceive(_:perform:) 订阅和更改一些状态变量 【参考方案1】:

你搜索的是什么?我无法测试它我不知道您使用的是什么库

class EquityViewMode: ObservableObject 
//    @Injected private var api: AlphaVantageAPI
    
    var pricePublisher: AnyPublisher<String, Never>
    @Published var price: String = ""

    init()
        
//         init your publisher like
//        pricePublisher = self.api.quote(symbol: self.code)
//            .share()
//            .map  "\($0?.price ?? 0) €" 
//            .eraseToAnyPublisher()




struct EquityView: View 

    @ObservedObject var viewModel: EquityViewModel
    var handle: AnyCancellable? = nil

    init(m:EquityViewMode) 
        viewModel = m
        handle = m.pricePublisher.assign(to: \.price, on: self.viewModel)
    

    var body: some View
        Text(viewModel.price)
    

【讨论】:

以上是关于使用 SwiftUI/Combine,如何避免在 ViewModel 中放置可取消项的主要内容,如果未能解决你的问题,请参考以下文章

使用 SwiftUI、Combine 和 Firebase,我如何在将用户的帐户链接到电子邮件/密码之前验证用户是不是已匿名登录?

忽略 TextField SwiftUI Combine 中输入的左侧空格

swiftui+combine:为啥滚动 LazyVGrid 时 isFavoriteO 改变了?

使用 SwiftUI+Combine 的数据库延迟加载

swiftui combine 无法获取数据 [关闭]

在需要@Binding 的地方传递@Published(SwiftUI、Combine)