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不会接收到任何输出

相关推荐
张龙6876 分钟前
构建生产级 AI Agent:工具调用与记忆架构实战指南
前端
kyriewen1 小时前
2026 年了,还在用 Node.js?Bun 迁移实战:20 分钟搞定,附踩坑记录
前端·javascript·node.js
青山Coding3 小时前
Cesium应用(八):物体运动的实现思路
前端·cesium
用户41659673693553 小时前
Android WebView 加载 file:// 离线页面调试教程
android·前端
Asmewill3 小时前
curl命令学习笔记一
前端
我是一只快乐的小螃蟹3 小时前
1.2 ArrayList 源码解析
前端
星栈3 小时前
我用 Rust + Dioxus 做了个全栈跨平台笔记应用:再把新建、编辑和交付补上
前端·rust·前端框架
我是一只快乐的小螃蟹3 小时前
1.1 HashMap (JDK1.8) 源码解析
前端
爱勇宝6 小时前
小红花成长新版:模板来了,鼓励也更容易开始
前端·后端·程序员
竹林8186 小时前
Solana前端开发:我在一个NFT铸造页面上被@solana/web3.js的Connection和Transaction签名坑了两天
前端