Combine知识点switchToLatest

使用场景

  1. 搜索框自动补全(避免发送多个请求,只保留最后一个)
  2. 异步任务切换(如下载任务,视频播放切换,当用户连续点击了多个视频时,只播放最后一个,取消前几个的请求)
  3. 网络请求取消(当用户发起新的请求时,自动取消旧的请求)
  4. 用户交互事件 (点击按钮触发任务,只执行最新的点击任务)

switchToLatest解决的核心问题是:

  1. 自动取消旧的Publisher,只保留最新的Publisher
  2. 适用于"每次订阅都会创建新的Publisher"的情况

switchToLatest的定义

swift 复制代码
func switchToLatest()-> Publishers.SwitchToLatest<Upstream.Output, Upstream>

这里的Upstream指的是Publisher内部产生的Publisher,switchToLatest会从中只取最新的Publisher。

核心特点

  1. 适合于Publisher<Publisher<Output, Failure>, Failure>,即一个Publisher产生多个Publisher的情况
  2. 当新的Publiser出现时,会自动取消之前的Publisher,只执行最新的publisher。
  3. 只会订阅最新的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"

代码解析

  1. searchText是一个PassthroughSubject,用于模拟用户输入的关键词
  2. map { query in searchAPI(query: query)} 每次输入都会调用searchAPI生成新的Publisher(异步网络请求)
  3. swiftToLatest() 只保留最新的Publisher,如果新的请求到来,会取消旧的请求
  4. 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不会接收到任何输出

相关推荐
程序员黑豆4 小时前
Java类型推断完全指南:从var到菱形运算符,掌握使用限制与最佳实践
java·前端·ai编程
To_OC4 小时前
踩了个 TS 的坑之后,我终于把 type 和 interface 掰明白了
前端·react.js·typescript
GreenTea5 小时前
深度解读 Anthropic 多智能体报告:更强的模型 ≠ 更好的协调
前端·后端·算法
浮生望5 小时前
前端API工程化:用 Mock 数据与 Axios 配置实现独立于后端的并行开发
前端
万少5 小时前
给 DeepSeek Harness 装个"应用商店":一条命令,595 个插件随你逛
前端·javascript·后端
波波0076 小时前
C# 15 重磅新特性: 带标签 break 与 continue:重新定义嵌套循环控制流
服务器·前端·c#
Brown.alexis6 小时前
es6知识点1-自备使用
前端·ecmascript·es6
小爬的老粉丝7 小时前
JavaScript 纯前端预览 WPS:先识别容器,再路由解析器
前端·javascript·wps
计算机魔术师8 小时前
新兴多智能体系统的模式与问题
前端
用户059540174468 小时前
AI Agent 上下文污染踩坑实录:用 pytest + Redis 揪出 3 类记忆串号 bug,排查 6 小时
前端·css