Swift Combine — Subject Publishers(PassthroughSubject & CurrentValueSubject)

本文主要介绍一下SubjectSubject 本身也是一个 Publisher,其定义如下:

public protocol Subject<Output, Failure> : AnyObject, Publisher {
	func send(_ value: Self.Output)
	func send(completion: Subscribers.Completion<Self.Failure>)
	func send(subscription: any Subscription)
}

从定义可以看到,Subject 暴露了三个 send 方法,外部调用者可以通过这些方法来主动地发布 output 值、failure 事件、 finished 事件以及subscription

Combine 内置提供了两种常用的 Subject 类型,分别是 PassthroughSubjectCurrentValueSubject,下面来分别看一下。

PassthroughSubject

PassthroughSubjectCombine框架中的一种Subject具体类型,它不持有任何值,将自己接收到的任何值简单的传递给下游的Subscriber

当我们要创建一个PassthroughSubject时,需要指定要发送的值的类型,然后使用send方法发送,任何的Subscriber都会收到这个值。因为它本身不持有值,所以如果下游没有Subscriber,那么这个值将废弃了。

  func testPassthroughSubjectPublisher() {

    let publish2 = PassthroughSubject<String, Error>()

    publish2.send("1")

    publish2
      .sink { completion in
        switch completion {
          case .finished:
            print("---> Finished")
          case .failure(let error):
            print("---> Error: \(error.localizedDescription)")
        }
      } receiveValue: { value in
        print("---> value is: \(value)")
      }
      .store(in: &cancellable)

    publish2.send("2")
    publish2.send(completion: .finished)

  }

上面代码中创建了PassthroughSubject实例publish2,并通过sink方法添加了订阅者,订阅后,随后通过send方法发送了2.finished事件,sink方法中对应的输出都正常。但是在添加sink方法之前发送的send("1")没有任何输出,就像上面说的,发送的时候还没有任何订阅者,发送的值就直接抛弃了。

输出结果:

---> value is: 2
---> Finished

作为Subject的具体实现,PassthroughSubject提供了一种方便的方法,使现有的命令式代码适应Combine模型。

CurrentValueSubject

CurrentvaluessubjectCombine框架中的一种Subject具体类型。它可以保存单个值,并在设置新值时向任何订阅者发布新值。
Currentvaluessubject在初始化的时候需要设置一个初始值。

let publisher = CurrentValueSubject<String, Error>("one")

当有订阅者订阅的时候会立即发送这个值。下面代码中当初始化CurrentValueSubjectViewModel的时候,则会直接输出“—> value is: one”

class CurrentValueSubjectViewModel: ObservableObject {

  init() {
    setUpPublisher()
  }

  func setUpPublisher() {
    let publisher = CurrentValueSubject<String, Error>("one")

    let cancelable = publisher
      .sink { completion in
        switch completion {
          case .finished:
            print("---> Finished")
          case .failure(let error):
            print("---> Error: \(error.localizedDescription)")
        }
      } receiveValue: { value in
        print("---> value is: \(value)")
      }
  }
}

下面代码中,在viewModel中实例化了一个CurrentValueSubject,并添加了subscriber,在SwiftUI界面添加了三个按钮,用来发送数据。

在发送数据的时候,可以通过send方法,也可以通过直接设置value的方法,效果都是一样的。

class CurrentValueSubjectViewModel: ObservableObject {
  private var cancellable = Set<AnyCancellable>()
  let publisher = CurrentValueSubject<String, Error>("one")

  init() {
    setUpPublisher()
  }

  func setUpPublisher() {
    publisher
      .sink { completion in
        switch completion {
          case .finished:
            print("---> Finished")
          case .failure(let error):
            print("---> Error: \(error.localizedDescription)")
        }
      } receiveValue: { value in
        print("---> value is: \(value)")
      }
      .store(in: &cancellable)
  }

  func sendMessage() {
    publisher.send("Hello World")
    publisher.value = "Swift Combine"
  }

  func sendError() {
    publisher.send(completion: .failure(NetworkError.invalidURL))
  }

  func sendFinished() {
    publisher.send(completion: .finished)
  }

}

struct CurrentValueSubjectDemo: View {
  @StateObject private var viewModel = CurrentValueSubjectViewModel()

  var body: some View {
    VStack {
      Button("Send Message") {
        viewModel.sendMessage()
      }

      Button("Send Finished") {
        viewModel.sendFinished()
      }

      Button("Send Error") {
        viewModel.sendError()
      }
    }
    .buttonStyle(BorderedProminentButtonStyle())
  }
}

当点击按钮时,输出如下:
在这里插入图片描述

关于Subject生命周期

Subject是有生命周期的,放发送了completion后(不管是finished还是error),Subject都不会再发送任何新值。

就上面的CurrentValueSubject为例,在发送一个value之后,就发送finished,然后在发送value就无效了。
在这里插入图片描述
PassthroughSubject亦是如此。所以当使用PassthroughSubjectCurrentValueSubject时,重要的是要考虑生命周期,并在明显没有任何值发送时关闭Subject

PassthroughSubject与CurrentValueSubject区别

首先这两个都是Subject的具体实现,都可以根据需要异步地无限地发出事件。这两个Subject的用法都比较简单,都作为Publisher发布数据,不过却别还是有的。

PassthroughSubject没有初始值,也不需要持有最近一次发布的值。
CurrentValueSubject可以为Publisher提供初始值,并通过更新 value属性自动发出事件。

网上有一个较为恰当的比喻:
PassthroughSubject就像一个门铃按钮。当有人按门铃时,只有当你在家时才会通知你。
CurrentValueSubject就像一个电灯开关。当你不在的时候灯是开着的,当你回家的时候你仍然会注意到它是开着的。

写在最后

本文主要介绍了PassthroughSubjectCurrentValueSubject的概念、使用以及一些区别,希望大家通过本文能对这两个Subject有个初步的了解和使用,文中如果有不对的地方,还望大家指正。

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值