使用场景
- 搜索框自动补全(避免发送多个请求,只保留最后一个)
- 异步任务切换(如下载任务,视频播放切换,当用户连续点击了多个视频时,只播放最后一个,取消前几个的请求)
- 网络请求取消(当用户发起新的请求时,自动取消旧的请求)
- 用户交互事件 (点击按钮触发任务,只执行最新的点击任务)
switchToLatest解决的核心问题是:
- 自动取消旧的Publisher,只保留最新的Publisher
- 适用于"每次订阅都会创建新的Publisher"的情况
switchToLatest的定义
swift
func switchToLatest()-> Publishers.SwitchToLatest<Upstream.Output, Upstream>
这里的Upstream指的是Publisher内部产生的Publisher,switchToLatest会从中只取最新的Publisher。
核心特点
- 适合于Publisher<Publisher<Output, Failure>, Failure>,即一个Publisher产生多个Publisher的情况
- 当新的Publiser出现时,会自动取消之前的Publisher,只执行最新的publisher。
- 只会订阅最新的Publiser并将其结果发送给下游
例1,防止重复的网络请求
场景
用户在搜索框中输入关键词时,每次输入都会触发新的API请求,我门希望只进行最新的一次请求,之前的取消。
swift
mport Combine
import Foundation
// 模拟搜索 API
func searchAPI(query: String) -> AnyPublisher<String, Never> {
Just("搜索结果: \(query)")
.delay(for: .seconds(1), scheduler: DispatchQueue.global()) // 模拟网络请求延迟
.eraseToAnyPublisher()
}
// 用户输入的搜索关键词
let searchText = PassthroughSubject<String, Never>()
// 处理搜索请求
let cancellable = searchText
.map { query in searchAPI(query: query) } // 每次输入都会生成一个新的 Publisher
.switchToLatest() // 只保留最新的 Publisher,取消旧的请求
.sink { result in
print(result) // 只会输出最新搜索的结果
}
// 模拟用户输入
searchText.send("Swift") // 请求 1
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
searchText.send("SwiftUI") // 请求 2,取消 "Swift"
}
DispatchQueue.global().asyncAfter(deadline: .now() + 1.5) {
searchText.send("Combine") // 请求 3,取消 "SwiftUI"
}
// 输出结果
"搜索结果: Combine"
代码解析
- searchText是一个PassthroughSubject,用于模拟用户输入的关键词
- map { query in searchAPI(query: query)} 每次输入都会调用searchAPI生成新的Publisher(异步网络请求)
- swiftToLatest() 只保留最新的Publisher,如果新的请求到来,会取消旧的请求
- slink订阅最终的结果,只会收到最新的搜索返回的值
例2, 点击按钮触发异步任务
场景:
用户点击下载按钮时,每次点击都睡触发新的下载任务,我们希望只有最后一次的任务执行,之前的任务自动取消。
swift
import Combine
import Foundation
// 模拟下载任务
func downloadTask(_ id: Int) -> AnyPublisher<String, Never> {
Just("任务 \(id) 下载完成")
.delay(for: .seconds(2), scheduler: DispatchQueue.global()) // 模拟下载延迟
.eraseToAnyPublisher()
}
// 用户点击按钮的触发器
let downloadButtonTap = PassthroughSubject<Int, Never>()
// 处理下载任务
let cancellable = downloadButtonTap
.map { id in downloadTask(id) } // 每次点击都会生成新的下载任务
.switchToLatest() // 只执行最新的任务,旧任务会被取消
.sink { result in
print(result) // 只会输出最新任务的完成结果
}
// 模拟用户点击
downloadButtonTap.send(1) // 任务 1
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
downloadButtonTap.send(2) // 任务 2,取消任务 1
}
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
downloadButtonTap.send(3) // 任务 3,取消任务 2
}
// 输出
// 任务3 下载完成
任务1 和 2 在完成之前被取消,所以他们的slink不会接收到任何输出