欢迎回来,这一节开始,我们先编写一个新的测试用例,验证 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
是一样的,只不过我们把订阅上游 Publisher
的 PassthroughSubject
换成了 MergeInput
。这次,我们就无需再手工管理用于订阅的 Cancellable
对象了,也不用再刻意构建 subject1
和 subject2
的事件内容了。当 subject1
和 subject2
结束之后,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
。
接下来,按照常理,后面就没有 subject
和 sink
什么事儿了。但这时,如果我们用 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 中有关重新订阅这个行为的研究。在众多发现里,最重要的一个,就是订阅代码有可能会把 Publisher
和 Subscriber
保持在内存里,进而造成意料之外的内存泄漏。使用 sink
方法订阅相对安全,因为它返回的 AnyCancellable
对象会自动取消订阅。而如果要使用自定义的 Subscribers.Sink
对象,就要格外小心,手工处理好 cancel
的调用。或者,确保事件序列的正常结束。
说到这,我们对 Combine 中的各种边缘情况,整理的就差不多了。接下来,就是最接近在泊学 App 开发中遇到的一个问题了。你认为一旦 Subscriber
订阅了 Publisher
之后,就可以收到它的所有事件了么?或者说,一旦 Subscriber
取消了订阅,它就再也不会订阅到到事件了么?
让人意外的是,在 Combine 里,这两个问题的答案都是 No。在接下来的内容里,我们就来研究下这些场景,导致问题发生的原因,以及避免的方法。