欢迎回来,这一节开始,我们先编写一个新的测试用例,验证 MergeInput 合并订阅多个 Publishers 的能力。在 CustomCombineTests.swift 里,添加下面的代码:

func testMergeInput() {
  let subject1 = PassthroughSubject<Int, Never>()
  let subject2 = PassthroughSubject<Int, Never>()
  let input = MergeInput<Int>()

  subject1.merge(into: input)
  subject2.merge(into: input)

  var received = [Subscribers.Event<Int, Never>]()

  let sink = input.sink(receiveCompletion: {
    received.append(.complete($0))
  }, receiveValue: {
    received.append(.value($0))
  })

  subject1.send(sequence: 1...2, completion: .finished)
  subject2.send(sequence: 3...4, completion: .finished)

  XCTAssertEqual(received, [1, 2, 3, 4].asEvents(completion: nil))

  sink.cancel()
}

大体上,它和 testMultipleSubjectSubscribe 是一样的,只不过我们把订阅上游 PublisherPassthroughSubject 换成了 MergeInput。这次,我们就无需再手工管理用于订阅的 Cancellable 对象了,也不用再刻意构建 subject1subject2 的事件内容了。当 subject1subject2 结束之后,received 收录的就是我们期望的 [1, 2, 3, 4] 事件。

无法“续命”的订阅

了解了同时订阅多个 Publisher 的行为之后,我们再来探讨另外一种情况:当一个订阅者订阅的 Publisher 结束之后,还可以用它继续订阅其它 Publisher 么?为了验证这个过程,我们先来实现这个测试用例的前半部分:

func testSinkReactivation() {
  var received = [Subscribers.Event<Int, Never>]()
  let sink = Subscribers.Sink<Int, Never>(receiveCompletion: {
    received.append(.complete($0))
  }, receiveValue: {
    received.append(.value($0))
  })

  weak var weakSubject: PassthroughSubject<Int, Never>?

  do {
    let subject = PassthroughSubject<Int, Never>()
    weakSubject = subject
    subject.subscribe(sink)

    subject.send(1)
  }

  XCTAssertNotNil(weakSubject)
}

其中,sink 是我们用于测试的订阅者,它订阅了 do 作用域中的 subject。按照之前说过的内容,离开作用域之后,subject 并不会被释放,订阅也依然有效,此时的 weakSubject 并不是 nil。接下来,通过 weakSubject,我们让事件序列结束:

func testSinkReactivation() {
  /// The same as before...

  weakSubject?.send(completion: .finished)
  XCTAssertNil(weakSubject)
  XCTAssertEqual(received, [1].asEvents(completion: .finished))
}

这次,weakSubject 就是 nil 了,并且 received 中应该可以收录到订阅的事件 1 以及 .finished

接下来,按照常理,后面就没有 subjectsink 什么事儿了。但这时,如果我们用 sink 重新订阅一个新的 Publisher 会如何呢?继续在 testSinkReactivation 里添加下面的代码试一下:

func testSinkReactivation() {
  /// The same as before...

  let subject2 = PassthroughSubject<Int, Never>()
  subject2.subscribe(sink)
  subject2.send(2)

  XCTAssertEqual(received, [1].asEvents(completion: .finished))
}

就像测试中判断依据所展示的,sink 已经无法再订阅到 subject2 中的事件。也就是说,一旦订阅结束了,就是结束了。不过要说明一下的是,这个行为仅在 mac OS 10.15 beta6 及以后的版本才生效。在 beta6 之前,sink6 是可以订阅到 subject2 的事件的。

What's next?

以上,就是所有关于 Combine 中有关重新订阅这个行为的研究。在众多发现里,最重要的一个,就是订阅代码有可能会把 PublisherSubscriber 保持在内存里,进而造成意料之外的内存泄漏。使用 sink 方法订阅相对安全,因为它返回的 AnyCancellable 对象会自动取消订阅。而如果要使用自定义的 Subscribers.Sink 对象,就要格外小心,手工处理好 cancel 的调用。或者,确保事件序列的正常结束。

说到这,我们对 Combine 中的各种边缘情况,整理的就差不多了。接下来,就是最接近在泊学 App 开发中遇到的一个问题了。你认为一旦 Subscriber 订阅了 Publisher 之后,就可以收到它的所有事件了么?或者说,一旦 Subscriber 取消了订阅,它就再也不会订阅到到事件了么?

让人意外的是,在 Combine 里,这两个问题的答案都是 No。在接下来的内容里,我们就来研究下这些场景,导致问题发生的原因,以及避免的方法。

所有订阅均支持 12 期免息分期

¥ 59

按月订阅

一个月,观看并下载所有视频内容。初来泊学,这可能是个最好的开始。

开始订阅

¥ 512

按年订阅

一年的时间,让我们一起疯狂地狩猎知识吧。比按月订阅优惠 28%

开始订阅

¥ 1280

泊学终身会员

永久观看和下载所有泊学网站视频,并赠送 100 元商店优惠券。

我要加入
如需帮助,欢迎通过以下方式联系我们