大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
如何避免不必要的计算,提升用户体验?
在开发中,我们经常遇到这样的问题:
「用户快速输入搜索关键字,但上一次搜索还没结束,新的一次请求已经发出,导致多个请求同时进行,造成性能浪费。」
「用户在数据加载过程中切换页面,后台任务还在继续运行,白白浪费了资源。」
这类问题的本质,就是 没有正确处理任务的取消。在 Swift 并发(Swift Concurrency)中,任务取消采用 协作式模型(cooperative cancellation),也就是说 系统不会强行停止你的任务,而是提供取消信息,至于如何处理,完全由你自己决定。
如果任务不主动检查 取消状态,即使已经被标记为“已取消”,它仍然会继续运行,造成资源浪费,甚至影响用户体验。
今天我们就来聊聊,Swift 并发中的任务取消该如何正确处理,避免性能浪费,提升 App 的流畅度。
任务取消的痛点分析
多次搜索导致的性能浪费
假设你在做一个搜索功能,用户每输入一个字母,都会触发一次网络请求。如果不取消之前的请求,就会造成多个请求同时运行,白白浪费网络和 CPU 资源。
表现:
- 用户搜索
apple
,输入a
、ap
、app
、appl
、apple
,触发 5 次搜索请求 - 5 个请求并发执行,即使
apple
的请求返回了,a
、ap
等请求依然会占用 CPU 资源
页面切换后,后台任务仍然在跑
比如一个健康数据查询功能,用户打开页面触发了一个异步查询,但在查询完成之前,用户已经返回了上一页。如果这个任务没有正确处理取消,它还是会继续运行,占用系统资源。
表现:
- 用户进入「健康数据」页面,触发查询任务
- 任务运行中,用户立刻返回主页,但查询任务仍然继续执行
用户手动取消任务后,任务仍然继续执行
有时候,我们会提供一个「取消」按钮,比如用户在下载一个大文件时,想要手动取消下载。如果我们只是标记任务为“已取消”,但没有在任务内部做检查,任务还是会继续下载,占用带宽和存储空间。
表现:
- 用户点击「开始下载」,触发一个任务
- 下载过程中,用户点击「取消」,但任务仍然继续下载
SwiftUI 任务取消的最佳实践
在 SwiftUI 中,我们可以使用 task(id:)
让任务在 id 变化时自动取消上一个任务,从而解决 搜索请求重复执行的问题。
搜索功能中的任务取消
struct ContentView: View {
@State private var store = Store()
@State private var query = ""
var body: some View {
NavigationStack {
List(store.results, id: \.self) { result in
Text(verbatim: result)
}
.searchable(text: $query)
.task(id: query) { // 👈 任务 id 变化时,自动取消前一个任务
await store.search(matching: query)
}
}
}
}
实际效果:
task(id: query)
绑定了搜索内容,每次query
变化时,都会取消上一个搜索任务并启动新的任务- 但是 上一个任务不会自动停止,它只是被标记为已取消,所以我们还需要在
search(matching:)
内手动检查取消状态
如何在任务内部正确处理中途取消?
Swift 提供了 Task.checkCancellation()
方法,它可以在任务被取消时抛出异常,让任务立即终止,避免不必要的计算。
在搜索任务中检查取消状态
import HealthKit
@MainActor @Observable final class Store {
private(set) var results: [HKCorrelation] = []
private let store = HKHealthStore()
func search(matching query: String) async {
let foodQuery = HKSampleQueryDescriptor(
predicates: [.correlation(type: .init(.food))],
sortDescriptors: []
)
do {
let food = try await foodQuery.result(for: store)
try Task.checkCancellation() // 👈 任务取消时,抛出异常,立即终止
results = food.filter { food in
let title = food.metadata?["title"] as? String ?? ""
return title.localizedStandardContains(query)
}
} catch {
results = []
}
}
}
这样做的好处:
- 任务取消后,不会继续执行过滤逻辑,避免多余计算
- 直接
catch
取消异常,清空results
,防止 UI 误显示旧数据
使用 Task.isCancelled
检查任务状态
除了 checkCancellation()
抛出异常外,我们还可以使用 Task.isCancelled
更温和地处理取消逻辑。
查询任务中使用 isCancelled
actor SearchService {
private var cachedResults: [HKCorrelation] = []
private let store = HKHealthStore()
func search(matching query: String) async throws -> [HKCorrelation] {
guard !Task.isCancelled else {
return cachedResults // 👈 任务被取消时,直接返回缓存结果
}
let foodQuery = HKSampleQueryDescriptor(
predicates: [.correlation(type: .init(.food))],
sortDescriptors: []
)
let food = try await foodQuery.result(for: store)
guard !Task.isCancelled else {
return cachedResults // 👈 再次检查取消状态,避免无效计算
}
cachedResults = food.filter { food in
let title = food.metadata?["title"] as? String ?? ""
return title.localizedStandardContains(query)
}
return cachedResults
}
}
适用场景:
- 需要返回部分结果,比如前一次请求的缓存数据,而不是直接报错退出
- 避免 UI 显示空数据,提升用户体验
手动取消任务的正确方式
有些场景下,我们可能需要提供一个「取消」按钮,让用户手动取消任务。
手动取消任务
struct ExampleView: View {
@State private var store = Store()
@State private var task: Task<Void, Never>?
var body: some View {
VStack {
Button("开始任务") {
task = Task {
await store.fetch()
}
}
Button("取消任务") {
task?.cancel() // 👈 只是标记任务为“已取消”,不会主动终止
}
}
}
}
但光这样是不够的,还需要在 fetch()
里面检查取消状态,否则任务仍然会继续运行!
总结
痛点回顾
- 搜索时,输入过快导致多个请求并发执行,浪费性能
- 用户离开页面,任务仍然在后台运行,影响系统资源
- 手动取消任务后,任务仍然继续执行,体验不佳
解决方案
- 在
task(id:)
绑定任务 ID,自动取消上一个任务 - 在任务内部使用
Task.checkCancellation()
或Task.isCancelled
主动检查取消状态 - 手动任务取消时,任务内部仍需自行处理取消逻辑
如果不正确处理任务取消,轻则浪费资源,重则影响用户体验,这些细节一定要掌握好!