Combine之Operator(switchToLatest)

1,044 阅读2分钟

switchToLatest是一个非常有意思的Operator,它在我们平时的开发中很常用。它接收publisher,输出具体的值,如下图所示:

Kapture 2020-11-19 at 10.12.50.gif

仔细观察上图,可以发现,当接收到新的publisher后,switchToLatest会指向新的publisher,并同时取消之前的publisher。

这一特性非常有用,比如,像下边这样的场景:

image.png

随着输入值的不断更新,会不断的触发新的网络请求,在这种情况下,我们想要的是最后一次网络请求,这种场景就是使用switchToLatest的最佳时机,正如switch to latest的翻译一样,它会切换到latest。

关于switchToLatest我们只要记住它的两个核心思想就行了:

  • 其输入为publihser,输出为具体的值,这就说明它会等待publisher的输出,不管publisher是即时的(Just(1))还是异步的(URLSession.shared.dataTaskPublisher(for: url))
  • 只保留最后的publisher,之前的publisher会自动取消

接下来,我们使用官方的一个例子来进一步讲解。

let subject = PassthroughSubject<Int, Never>()

cancellable = subject
    .setFailureType(to: URLError.self)
    .map() { index -> URLSession.DataTaskPublisher in
        let url = URL(string: "https://example.org/get?index=\(index)")!
        return URLSession.shared.dataTaskPublisher(for: url)
    }
    .switchToLatest()
    .sink(receiveCompletion: {
        print("Complete: \($0)")
    }, receiveValue: { (data, response) in
        guard let url = response.url else {
            print("Bad response.")
            return
        }
        print("URL: \(url)")
    })

for index in 1...5 {
    DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(index / 10)) {
        subject.send(index)
    }
}

打印结果如下:

URL: https://example.org/get?index=5

上边的例子中,我们使用PassthroughSubject来发送数据,接下来通过map把1~5转换成网络请求,由于网络请求是有一定延时的,所以只输出了最后发送的数据,因为前边的请求都被取消了,上边的情况正好验证了switchToLatest的含义。

那么如果上边例子中的publisher不是异步的会怎样呢?

cancellable = subject
    .setFailureType(to: URLError.self)
    .map() { index -> Just<Int> in
        Just(index)
    }
    .switchToLatest()
    .sink(receiveCompletion: {
        print("Complete: \($0)")
    }, receiveValue: { value in
        print("Value: \(value)")
    })

for index in 1...5 {
    DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(index / 10)) {
        subject.send(index)
    }
}

打印结果为:

Value: 1
Value: 2
Value: 3
Value: 4
Value: 5

到目前为止,我们基本上讲明白了switchToLatest的用法,与它相对应的是flatMap,它返回一个publisher,当该publisher有值的时候,就会流向下游,flatMap同样是一个非常有意思的Operator。我们把上边的代码稍微改动一下:

let subject = PassthroughSubject<Int, Never>()

cancellable = subject
    .setFailureType(to: URLError.self)
    .flatMap { index -> URLSession.DataTaskPublisher in
        let url = URL(string: "https://example.org/get?index=\(index)")!
        return URLSession.shared.dataTaskPublisher(for: url)
    }
    .sink(receiveCompletion: {
        print("Complete: \($0)")
    }, receiveValue: { (data, response) in
        guard let url = response.url else {
            print("Bad response.")
            return
        }
        print("URL: \(url)")
    })

for index in 1...5 {
    DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(index / 10)) {
        subject.send(index)
    }
}

打印结果如下:

URL: https://example.org/get?index=5
URL: https://example.org/get?index=1
URL: https://example.org/get?index=2
URL: https://example.org/get?index=3
URL: https://example.org/get?index=4

打印的顺序依赖每个网络请求的速度,速度快的就会先回来。

总结一下,switchToLatest的核心思想是保留最后一个publisher,在实际开发中,特别适合用于过滤搜索框的多余的网络请求。