Advertisement

SwiftUI Combine 的 PassthroughSubject

阅读量:

PassthroughSubject,本质是把一个数据,变为Publisher

以一个数组publisher为例

复制代码
    let fibonacciPublisher = [0,1,1,2,3,5].publisher

查看fibonacciPublisher(option+鼠标点击),他是Publishers.Sequence<[Int], Never>的类型,是将Sequence转换后的publisher,我们可以用sink的方法,订阅并接收他发布的数据

复制代码
 fibonacciPublisher

    
  
    
      .sink(receiveCompletion: { completion in
    
  
    
          switch(completion) {
    
  
    
              case .finished:
    
  
    
                  print("finished")
    
  
    
              case .failure(let error):
    
  
    
                  print(error.localizedDescription)
    
  
    
          }
    
  
    
      }, receiveValue: { value in
    
  
    
          print(value)
    
  
    
      })

打印结果

复制代码
 0

    
  
    
 1
    
  
    
 1
    
  
    
 2
    
  
    
 3
    
  
    
 5
    
  
    
 finished

结果表明了两个信息:
1、Sequence的数据逐个被发布并接收
2、数据全部发布完毕后,结束了这次订阅

实际应用中,我们很少会用到这种Publisher,我们用到比较多的是PassthroughtSubject 和 CurrentValueSubject

PassthroughSubject

我们用PassthroughSubject发布一个同样的数组

复制代码
 let pubFibonacci = PassthroughSubject<[Int], Never>()

    
  
    
 pubFibonacci
    
  
    
      .sink(receiveCompletion: { completion in
    
  
    
          switch(completion) {
    
  
    
          case .finished:
    
  
    
              print("finished")
    
  
    
          case .failure(let error):
    
  
    
              print(error.localizedDescription)
    
  
    
          }
    
  
    
      }, receiveValue: { value in
    
  
    
          print(value)
    
  
    
      })

PassthroughtSubject包含两个参数,第一个是发布的数据类型,这里我们要发布整型数组,所以填写[Int],第二个是错误类型,我们默认他永远不会发布错误,所以填写 Never

运行观察打印,发现没有任何输出信息。因为PassthroughSubject并没有发布任何信息,他和Sequence的publisher不同,PassthrougthSubject需要手动发布数据,这个特性也使得他可以灵活的运用到我们的App工程中。

加入发送代码

复制代码
    pubFibonacci.send([0,1,1,2,3,5])

这时候打印信息如下:

复制代码
  
    
 [0, 1, 1, 2, 3, 5]

可以看到和Sequence不同,PassthroughSubject一次性发布了所有数据,同时,没有结束订阅过程。
再次调用send发布另一个数组数据

复制代码
    pubFibonacci.send([7,9,13,18,21])

打印信息如下:

复制代码
    [7, 9, 13, 18, 21]

订阅的过程仍在继续。
如果想要订阅结束,我们需要手动发布一个结束的指令

复制代码
    pubFibonacci.send(completion: .finished)

打印信息如下:

复制代码
    finished

此后再用send发布数据,也不会有打印了,因为订阅已经结束了。
注意,我们开始声明的PassthroughSubject的错误类型是Never,默认不会发布错误。实际应用中,大多数情况下会有错误的情况,比如网络请求的错误等,我们将其改为Error```

复制代码
 // MARK: - 声明一个符合Error协议的自定义Error

    
 enum Error: Swift.Error {
    
  
    
  case someError
    
  
    
  }
    
  
    
  
    
 let pubFibonacci = PassthroughSubject<[Int], MyError>()
    
  
    
 pubFibonacci
    
  
    
      .sink(receiveCompletion: { completion in
    
  
    
          switch(completion) {
    
  
    
          case .finished:
    
  
    
              print("finished")
    
  
    
          case .failure(let error):
    
  
    
              print("Received error: \(error)")
    
  
    
          }
    
  
    
      }, receiveValue: { value in
    
  
    
          print(value)
    
  
    
      })
    
  
    
 // 正常发布
    
 pubFibonacci.send([0,1,1,2,3,5])
    
  
    
   
    
 // 发布错误
    
 pubFibonacci.send(completion: .failure(.someError))
    
  
    
 // 正常发布
    
 pubFibonacci.send([7,9,13,18,21])

这时我们的打印信息

复制代码
 [0, 1, 1, 2, 3, 5]

    
  
    
 Received error: someError

订阅者接收到了错误,订阅终止,后续发布的数据也无法被接收到了。

实战应用

上面我们介绍了PassthroughSubject的基本概念和功能,我们来看实际项目中如何使用。

在实际项目中,PassthroughSubject,或者可以说所有的Publisher,大多数情况,都是用来

将变量,转化为publisher,

这点非常重要。转化为publisher后,我们可以接收,或者说监听变量的变化情况,来进行即时的处理,也就是我们常说的异步。

我们新建一个主View,显示水果列表和导航栏的右侧按钮

复制代码
 import SwiftUI

    
 import Combine
    
  
    
 struct PassthroughSubjectView: View {
    
     
    
     @StateObject var vm = ViewModel()
    
     @State var isSheet: Bool = false
    
     @State var newFruit: String = ""
    
     
    
     var body: some View {
    
     NavigationView {
    
         VStack {
    
             List(vm.listText, id: \.self) { t in
    
                 Text(t)
    
             }
    
         }
    
         .navigationBarTitle("我喜欢的水果")
    
         .navigationBarItems(trailing: Button(action: {
    
             self.isSheet.toggle()
    
         }, label: {
    
             Image(systemName: "plus.circle")
    
         }))
    
         .sheet(isPresented: $isSheet, content: {
    
             SheetView()
    
         })
    
     }
    
     
    
     }
    
 }
    
  
    
 extension PassthroughSubjectView {
    
  
    
     class ViewModel: ObservableObject {
    
     @Published var searchText: String = ""
    
     @Published var listText: [String] = [
    
         "Banana", "Apple", "Cherry", "Watermelon"
    
     ]
    
     }
    
 }

再建立一个子View,用来底部弹出显示,子View中包含一个水果输入框和一个按钮,点击按钮我们想要将新输入的水果,显示到我们的主View中。

复制代码
 extension PassthroughSubjectView {

    
     
    
     func SheetView() -> some View {
    
     
    
     VStack {
    
         TextField("Add fruit...", text: $newFruit)
    
             .padding()
    
             .background(Color.gray.opacity(0.2))
    
             .padding()
    
         
    
         Button(action: {
    
       
    
             isSheet.toggle()
    
         }, label: {
    
             Text("Add")
    
         })
    
     }
    
     
    
     }
    
 }

现在我们用Combine的PassthroughSubject实现这一功能

在ViewModel中,定义一个publisher

复制代码
    var addFruit = PassthroughSubject<String, Never>()

在初始化时订阅这个publisher

复制代码
  var cancellable: AnyCancellable?

    
     
    
  init() {
    
     cancellable = addFruit
    
      .print("_add fruit_")
    
      .sink { [weak self] fruit in
    
          self?.listText.append(fruit)
    
       }
    
   }

在我们子View的按钮“Add”被点击时,我们将用户新输入的内容,发布给订阅者。

复制代码
 Button(action: {

    
             // 发布订阅值
    
             vm.addFruit.send(newFruit)
    
             isSheet.toggle()
    
         }, label: {
    
             Text("Add")
    
         })

此时,ViewModel中的addFruit.sink开始接收数据,并将数据增加到listText数组中

控制台调试信息如下

复制代码
 _add fruit_: receive subscription: (PassthroughSubject)

    
 _add fruit_: request unlimited
    
 _add fruit_: receive value: (Grace)

这就是一个PassthroughSubject在实际应用中的一个简单操作。

我们将子View中输入框内容的字符串,包装成,或者说转化为publisher,让Combine可以工作,可以在ViewModel中接收他发布的内容,然后进行处理。

这是一个异步过程,也是响应式编程的思想。

全部评论 (0)

还没有任何评论哟~