Skip to content

Repository files navigation

AsyncSequence Lifecycle Lab

A small Swift 6 command-line lab for a 30-minute discussion about long-lived for await loops. It turns the claims in Michael Long's Using Async For/Await? You're Probably Doing It Wrong into observable code and tests.

The point is not that for await is unsafe. The point is that convenient syntax does not define ownership, termination, producer cleanup, buffering, or actor isolation for you.

Requirements

  • macOS 14 or newer
  • A Swift 6 toolchain
  • Xcode if you want to present with editor tabs and Command-R

There are no third-party dependencies.

Run it

From Terminal:

swift run AsyncSequenceLabDemo long-lived-task
swift run AsyncSequenceLabDemo weak-self-trap
swift run AsyncSequenceLabDemo owned-task
swift run AsyncSequenceLabDemo producer-termination
swift run AsyncSequenceLabDemo backpressure
swift run AsyncSequenceLabDemo latest-value-wins

Run every chapter as a quick rehearsal:

swift run AsyncSequenceLabDemo all

Validate the observations:

swift test

For Xcode, open Package.swift, select the AsyncSequenceLabDemo scheme and the local Mac destination, then change selectedDemo in Sources/AsyncSequenceLabDemo/AsyncSequenceLabDemo.swift before pressing Command-R.

The five core experiments and optional appendix

Chapter What the running code proves
long-lived-task A live task can retain its captured owner until the sequence finishes.
weak-self-trap An outer guard let self recreates a strong reference across every suspension; a weak per-event access releases the owner but does not terminate the task.
owned-task Storing and cancelling the task handle works when the task closure does not strongly retain its owner.
producer-termination Consumer cancellation must reach callback-side resources through onTermination.
backpressure Buffering policy decides which values wait, disappear, or accumulate.
latest-value-wins Cancelling the previous operation ensures only the newest value completes when handlers cooperate with cancellation.

The examples use controlled streams and weak lifetime probes so the results are deterministic rather than timing anecdotes. The same observations are encoded as Swift Testing assertions in Tests/AsyncSequenceLabTests.

A useful correction to the article's shorthand

The opening example definitely risks keeping an object alive indefinitely, but "reference cycle" is not always the most precise diagnosis. A live unstructured task is enough to retain its closure, which can retain self; an object-to-object cycle is not required. A real cycle does exist when self stores the task handle and the task closure strongly captures self:

self -> task handle -> task closure -> self

That distinction matters because [weak self] can fix the ownership edge while still leaving an orphaned task suspended forever. The review question is not only "is the capture weak?" but also "who owns this task, and what ends it?"

Presentation material

DEMO_SCRIPT.md is the presenter guide. It contains:

  • a rehearsed 30-minute timeline;
  • prediction prompts before each run;
  • expected console output;
  • review rules and likely senior-level questions;
  • optional SwiftUI, actor-isolation, detached-task, and latest-value-wins appendices;
  • a recovery path if Xcode misbehaves during the session.

The main talk deliberately uses five small files instead of one large sample so each lifecycle decision remains visible in code review.

For a browser-rendered version with a table of contents, cue styling, dark mode, and print/PDF support, open DEMO_SCRIPT.html. Regenerate it after editing the Markdown source with:

node Scripts/render-demo-script.cjs

For the live session, DEMO_NOTES.html is a compact landscape one-pager containing only the timing, prompts, expected observations, takeaways, and recovery commands.

About

Runnable Swift 6 lab for AsyncSequence ownership, cancellation, cleanup, and backpressure

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages