swift-concurrency-extras

swift-concurrency-extras

增强Swift并发代码的可测试性与可靠性工具库

swift-concurrency-extras是一个开源库,为Swift并发编程提供实用工具集。该库增强了Swift并发代码的可测试性和可靠性,包含LockIsolated类型用于安全访问共享状态、Stream和Task相关辅助函数,以及串行执行工具。它主要面向需要编写可靠并发代码和单元测试的开发者,有助于简化异步编程中常见挑战的处理过程。

Swift并发编程测试异步编程开源库Github开源项目

swift-concurrency-extras

CI Slack

可靠且可测试的Swift并发。

了解更多

这个库旨在支持为Point-Free制作的库和剧集,Point-Free是一个由Brandon WilliamsStephen Celis主持的探索Swift编程语言的视频系列。

你可以在这里观看所有剧集。

<a href="https://www.pointfree.co/collections/concurrency/"> <img alt="视频海报图片" src="https://yellow-cdn.veclightyear.com/2b54e442/888106b8-0f78-4ea2-89fe-b7e23f5ed500.jpeg" width="600"> </a>

动机

这个库提供了许多工具,使Swift并发变得更容易使用和更易于测试。

LockIsolated

LockIsolated类型帮助将其他值包装在隔离的上下文中。它使用锁将值包装在一个类中,允许你通过同步接口读写该值。

该库为两种Swift流类型提供了多个辅助API:

  • 有一些辅助函数可以将任何AsyncSequence符合性擦除为任一具体流类型。这允许你将流类型视为某种"类型擦除"的AsyncSequence

    例如,假设你有这样一个依赖客户端:

    struct ScreenshotsClient { var screenshots: () -> AsyncStream<Void> }

    然后你可以构建一个将NotificationCenter.Notifications异步序列"擦除"为流的活跃实现:

    extension ScreenshotsClient { static let live = Self( screenshots: { NotificationCenter.default .notifications(named: UIApplication.userDidTakeScreenshotNotification) .map { _ in } .eraseToStream() // ⬅️ } ) }

    使用eraseToThrowingStream()来传播来自抛出异常的异步序列的失败。

  • Swift 5.9的makeStream(of:)函数已经被向后移植。它在需要覆盖返回流的依赖端点的测试中很有用:

    let screenshots = AsyncStream.makeStream(of: Void.self) let model = FeatureModel(screenshots: { screenshots.stream }) XCTAssertEqual(model.screenshotCount, 0) screenshots.continuation.yield() // 模拟截图 XCTAssertEqual(model.screenshotCount, 1)
  • 提供了静态的AsyncStream.neverAsyncThrowingStream.never辅助函数,它们表示永远存在且从不发出的流。它们在需要用永远挂起且不发出的流覆盖依赖端点的测试中很有用。

    let model = FeatureModel(screenshots: { .never })
  • 提供了静态的AsyncStream.finishedAsyncThrowingStream.finished(throwing:)辅助函数,它们表示立即完成且不发出的流。它们在需要用立即完成/失败的流覆盖依赖端点的测试中很有用。

任务

该库为Task类型增加了新功能。

  • 静态函数Task.never()可以异步返回任何类型的值,但通过永久挂起来实现。这对于以不需要实际从该端点返回数据的方式满足依赖要求很有用。

    例如,假设你有这样一个依赖客户端:

    struct SettingsClient { var fetchSettings: () async throws -> Settings }

    你可以在测试中通过等待Task.never()来覆盖客户端的fetchSettings端点,使其永久挂起:

    SettingsClient( fetchSettings: { try await Task.never() } )
  • Task.cancellableValue是一个属性,它等待非结构化任务的value属性,同时从当前异步上下文传播取消。

  • Task.megaYield()是一个粗暴的工具,可以通过多次挂起当前任务来使不稳定的异步测试变得稳定一些,增加其他异步工作有足够时间启动的机会。在可能的情况下,优先考虑使用串行执行的可靠性。

串行执行

由于运行时处理挂起点的方式,Swift中的一些异步代码notoriously难以测试。该库提供了一个静态函数withMainSerialExecutor,它试图串行和确定性地运行操作中产生的所有任务。这个函数可以用来使异步测试更快、更稳定。

警告:这个API仅用于测试,以使测试更可靠。请不要在应用程序代码中使用它。

我们说它"_尝试_串行和确定性地运行操作中产生的所有任务",因为它在底层依赖Swift运行时中的一个全局可变变量来完成工作,如果这个可变变量在操作期间发生变化,就无法保证其作用域。

例如,考虑以下看似简单的模型,它发起网络请求并在请求进行时管理isLoading状态:

@Observable class NumberFactModel { var fact: String? var isLoading = false var number = 0 // 显式注入请求依赖以使其可测试,也可以通过依赖管理库提供 let getFact: (Int) async throws -> String func getFactButtonTapped() async { self.isLoading = true defer { self.isLoading = false } do { self.fact = try await self.getFact(self.number) } catch { // TODO: 处理错误 } } }

我们希望能够编写一个测试来确认isLoading状态先变为true然后变为false。你可能希望它像这样简单:

func testIsLoading() async { let model = NumberFactModel(getFact: { "\($0) is a good number." }) let task = Task { await model.getFactButtonTapped() } XCTAssertEqual(model.isLoading, true) XCTAssertEqual(model.fact, nil) await task.value XCTAssertEqual(model.isLoading, false) XCTAssertEqual(model.fact, "0 is a good number.") }

然而,这几乎100%会失败。问题在于,创建非结构化Task后的下一行会在非结构化任务内部的行之前执行,所以我们永远不会检测到isLoading状态变为true的时刻。

你可能希望通过使用Task.yield来在getFactButtonTapped方法被调用和请求完成之间插入一些时间:

func testIsLoading() async { let model = NumberFactModel(getFact: { "\($0) is a good number." }) let task = Task { await model.getFactButtonTapped() } + await Task.yield() XCTAssertEqual(model.isLoading, true) XCTAssertEqual(model.fact, nil) await task.value XCTAssertEqual(model.isLoading, false) XCTAssertEqual(model.fact, "0 is a good number.") }

但这仍然在绝大多数情况下失败。

这些问题以及更多问题可以通过在主串行执行器上运行整个测试来解决。你还需要在getFact端点中插入一个小的yield,因为Swift能够内联不实际执行异步工作的异步闭包:

func testIsLoading() async { + await withMainSerialExecutor { let model = NumberFactModel(getFact: { + await Task.yield() return "\($0) is a good number." }) let task = Task { await model.getFactButtonTapped() } await Task.yield() XCTAssertEqual(model.isLoading, true) XCTAssertEqual(model.fact, nil) await task.value XCTAssertEqual(model.isLoading, false) XCTAssertEqual(model.fact, "0 is a good number.") + } }

这个小小的改变使得这个测试100%确定性地通过。

文档

该库的最新文档可在此处获取。

致谢

感谢Pat Brown和Thomas Grapperon在发布前对该库提供反馈。特别感谢Kabir Oberai帮助我们解决了Xcode的一个bug,并使我们能够在库中提供串行执行工具。

其他库

Concurrency Extras只是让Swift中编写可测试代码变得更容易的众多库之一。

  • Case Paths:用于处理和测试枚举的工具。

  • Clocks:一些时钟,使Swift并发更易于测试和更加多功能。

  • Combine Schedulers:一些调度器,使Combine更易于测试和更加多功能。

  • Composable Architecture:一个库,用于以一致且可理解的方式构建应用程序,考虑到了组合、测试和人体工程学。

  • Custom Dump:用于调试、比较和测试应用程序数据结构的工具集合。

  • Dependencies:受SwiftUI的"环境"启发的依赖管理库。

  • Snapshot Testing:通过记录和对比制品来断言你的应用程序。

  • XCTest Dynamic Overlay:从应用程序代码调用XCTFail和其他通常仅用于测试的辅助函数。

许可证

该库根据MIT许可证发布。详情请参见LICENSE

编辑推荐精选

音述AI

音述AI

全球首个AI音乐社区

音述AI是全球首个AI音乐社区,致力让每个人都能用音乐表达自我。音述AI提供零门槛AI创作工具,独创GETI法则帮助用户精准定义音乐风格,AI润色功能支持自动优化作品质感。音述AI支持交流讨论、二次创作与价值变现。针对中文用户的语言习惯与文化背景进行专门优化,支持国风融合、C-pop等本土音乐标签,让技术更好地承载人文表达。

QoderWork

QoderWork

阿里Qoder团队推出的桌面端AI智能体

QoderWork 是阿里推出的本地优先桌面 AI 智能体,适配 macOS14+/Windows10+,以自然语言交互实现文件管理、数据分析、AI 视觉生成、浏览器自动化等办公任务,自主拆解执行复杂工作流,数据本地运行零上传,技能市场可无限扩展,是高效的 Agentic 生产力办公助手。

lynote.ai

lynote.ai

一站式搞定所有学习需求

不再被海量信息淹没,开始真正理解知识。Lynote 可摘要 YouTube 视频、PDF、文章等内容。即时创建笔记,检测 AI 内容并下载资料,将您的学习效率提升 10 倍。

AniShort

AniShort

为AI短剧协作而生

专为AI短剧协作而生的AniShort正式发布,深度重构AI短剧全流程生产模式,整合创意策划、制作执行、实时协作、在线审片、资产复用等全链路功能,独创无限画布、双轨并行工业化工作流与Ani智能体助手,集成多款主流AI大模型,破解素材零散、版本混乱、沟通低效等行业痛点,助力3人团队效率提升800%,打造标准化、可追溯的AI短剧量产体系,是AI短剧团队协同创作、提升制作效率的核心工具。

seedancetwo2.0

seedancetwo2.0

能听懂你表达的视频模型

Seedance two是基于seedance2.0的中国大模型,支持图像、视频、音频、文本四种模态输入,表达方式更丰富,生成也更可控。

nano-banana纳米香蕉中文站

nano-banana纳米香蕉中文站

国内直接访问,限时3折

输入简单文字,生成想要的图片,纳米香蕉中文站基于 Google 模型的 AI 图片生成网站,支持文字生图、图生图。官网价格限时3折活动

扣子-AI办公

扣子-AI办公

职场AI,就用扣子

AI办公助手,复杂任务高效处理。办公效率低?扣子空间AI助手支持播客生成、PPT制作、网页开发及报告写作,覆盖科研、商业、舆情等领域的专家Agent 7x24小时响应,生活工作无缝切换,提升50%效率!

堆友

堆友

多风格AI绘画神器

堆友平台由阿里巴巴设计团队创建,作为一款AI驱动的设计工具,专为设计师提供一站式增长服务。功能覆盖海量3D素材、AI绘画、实时渲染以及专业抠图,显著提升设计品质和效率。平台不仅提供工具,还是一个促进创意交流和个人发展的空间,界面友好,适合所有级别的设计师和创意工作者。

图像生成AI工具AI反应堆AI工具箱AI绘画GOAI艺术字堆友相机AI图像热门
码上飞

码上飞

零代码AI应用开发平台

零代码AI应用开发平台,用户只需一句话简单描述需求,AI能自动生成小程序、APP或H5网页应用,无需编写代码。

Vora

Vora

免费创建高清无水印Sora视频

Vora是一个免费创建高清无水印Sora视频的AI工具

下拉加载更多