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

相关推荐
im_AMBER38 分钟前
React 12
前端·javascript·笔记·学习·react.js·前端框架
开开心心就好38 分钟前
电脑音质提升:杜比全景声安装详细教程
java·开发语言·前端·数据库·电脑·ruby·1024程序员节
午安~婉1 小时前
HTML CSS八股
前端·css·html
有事没事实验室1 小时前
css变量
前端·css
前端付豪1 小时前
Vue 中的 JSX:让组件渲染更灵活的正确方式
前端·javascript·vue.js
在泡泡里1 小时前
前端规范【四】eslint(antfu)、lefthook、commitlint
前端
烛阴1 小时前
Python 几行代码,让你的照片秒变艺术素描画
前端·python
Jolyne_1 小时前
如何实现无感刷新Token
前端
用户4099322502122 小时前
Vue3响应式系统的底层原理与实践要点你真的懂吗?
前端·ai编程·trae
qq_479875432 小时前
RVO和移动语义
前端·算法