【问题标题】:is it normal that number of cancellable items is getting higher?可取消项目的数量越来越多是否正常?
【发布时间】:2021-06-14 02:25:27
【问题描述】:

我有一个 SwiftUI 登录视图,其中包含 2 个用于用户名、密码和登录按钮的文本字段。我正在使用 MVVM 设计模式,并希望我的 viewModel 在用户单击登录按钮后进行身份验证。

视图的实现如下

struct LoginView: View {
    
    @ObservedObject var viewModel = LoginViewModel()
    
    var body: some View {
        
        VStack(alignment: .leading) {
            
            TextField("Enter e-mail address", text: $viewModel.email)
            TextField("Enter password", text: $viewModel.password)
            
            Button("Login") {
                viewModel.login()
            }
        }
    }
}

和视图模型

class LoginViewModel: ObservableObject {
    
    @Published var email: String = ""
    @Published var password: String = ""
    
    var authenticate = PassthroughSubject<User, Never>()
    var cancellables = Set<AnyCancellable>()
    
    init() {
        authenticate.sink { user in
            Webservice().authenticate(username: user.username, passsword: user.password).sink { error in
                //
            } receiveValue: { token in
                //
            }.store(in: &self.cancellables)
        }.store(in: &cancellables)
    }
    
    func login() {
        authenticate.send(User(username: email, password: password))
    }
}

一切正常,但每次用户单击登录按钮时,可取消项的数量都会增加。我想这不应该是这样的。这里有什么问题?

【问题讨论】:

  • 如果答案解决了您的问题(other question 中的代码似乎就是这种情况),那么请将答案标记为接受以关闭此问题。

标签: swift mvvm combine


【解决方案1】:

订阅数量不应随着每个发出的值而增加。在您的情况下它确实增加的原因是因为您在 sink 中有一个 sink - 对于每个 authenticate.send() 发出的值,内部的 sink 可取消添加到集合中。

但是这个嵌套的sink 不是解决这个问题的正确方法。相反,您应该通过将发出User 值的authenticate 链接到创建一个新发布者的东西来创建一个管道,该发布者实际上执行“转换”User(通过发出登录Web 请求)到令牌的工作。

您通常可以使用flatMap 进行这种链接,但在您的情况下,您希望取消之前的请求,因此您应该使用mapswitchToLatest 的组合:

authenticate
   .map { user in 
      Webservice().authenticate(username: user.username, password: user.password)
   }
   .switchToLatest()
   .sink(receiveCompletion: { completion in
      //
   }, receiveValue: { token in 
      //
   })
   .store(in: &cancellables)

这是一个以authenticate.send 开头并在结尾处获取令牌作为值的单一订阅。

【讨论】:

    【解决方案2】:

    每次点击登录:

    1. 您在authenticate 中发送了一个新用户事件
    2. authenticate 的接收器中,创建了一个全新的请求并将其存储到cancellables。

    第二点是cancellables 增加的地方,在得到响应后可能已经是completed,但会随着cancellables 的释放而被清理。这很正常。

    但可能不正常的是,当您请求新的登录尝试时,您最好取消之前的登录尝试(如果尚未完成)。如果您想这样做,请将该可取消与 Web 服务分开,并在尝试新实例之前取消之前的实例。

    private var loginCancellable: AnyCancellable?
    
    // ... in init
    authenticate.sink { user in
        loginCancellable.cancell()
        loginCancellable = Webservice().authenticate(username: user.username, passsword: user.password).sink { error in
            //
        } receiveValue: { token in
            //
        }
    // ... rest of the code    
    
    

    注意:编写所有这些代码只是我的头脑。未测试。你可能需要稍微调整一下。但希望你能明白。

    更新: New Dev's Answer 是你应该去的。 RxSwift 中的 FlatMapLatest 本质上表示为 Combine 中的 map { .. }.switchToLatest()。作为一个普遍的想法,当以反应方式工作时,总是喜欢链式解决方案而不是封闭式解决方案。我上面的方法可以给你一个想法,你在概念上缺少什么。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-26
      • 2015-02-21
      • 2019-05-15
      相关资源
      最近更新 更多