Your Swift code compiles. Strict concurrency is happy. Your actors isolate their state. Your tasks respond to cancellation.
And your architecture can still be wrong.
The hardest concurrency question is often not where does this task execute? It is:
Who owns the work — and when does that work stop mattering?
Modern Swift gives us structured concurrency, Task, actors, Sendable, AsyncSequence, task groups, and cooperative cancellation. None of those mechanisms, however, can decide what an operation means to the product.
A view disappearing is a UI event. A task being cancelled is a concurrency event. A business operation becoming irrelevant is a domain event.
Sometimes those events should happen together. Sometimes they absolutely should not.
The architecture is the decision that connects them.
Correct Swift Can Still Encode the Wrong Architecture
Consider a live-session feature:

This is idiomatic SwiftUI. But there is an architectural statement hidden inside it:

By placing the operation inside .task, we have associated its lifetime with this presentation.
That might be correct. But what if observeSession() maintains a live connection, persists events, tracks an activity already started by the user, synchronizes a shared resource, completes an upload, or observes a transaction that must reach a terminal state?
Why should the lifetime of that domain operation be determined by whether one SwiftUI view is visible?
A Task is a unit of execution. It is not necessarily a unit of product work.
The Lifetimes We Keep Collapsing

A view is presentation. A task is execution. A feature owns user-facing state and behavior. A resource might be a stream, sensor, file handle, subscription, or connection. A domain operation is the thing the user actually asked the product to do.
Suppose a user starts uploading a large video and switches tabs. The presentation changed. The product intent probably did not.
If our architecture equates those lifetimes, the UI accidentally becomes the owner of the business operation.
Start With Intent, Not With Task
Before creating a task, model the operation:

For a live session:

Instead of asking "Where should I put my Task?", ask:
I know what owns this operation. How should Swift express that lifetime?
The code becomes a consequence of the architecture.
Cancellation Is Product Semantics
Four operations can all begin with Task { ... } and still have completely different cancellation semantics.
Search — superseded intent

Here cancellation means: this intent is obsolete.
Preview rendering — no remaining consumer
If an editor closes and nobody needs an expensive preview, cancellation can mean: the result no longer has a consumer.
The same rule applies frame by frame in a Core ML or Vision pipeline. If the frame an inference was analysing has already been replaced, the result has no consumer. Dropping or cancelling that work is pure gain.
Upload — navigation is not cancellation

The important change is not the actor. It is ownership.
There is one more owner above UploadManager: the operating system. An in-process Task survives navigation, but it does not survive suspension or termination. For a large video, the transport belongs to a background URLSession (URLSessionConfiguration.background(withIdentifier:), uploading from a file), which keeps transferring while the app is suspended and relaunches it to deliver completion events.
UploadManager then owns the product view of the upload — identity, progress, retries, user cancellation — while the system owns the transport. Two owners, two lifetimes, both explicit. (If the user force-quits the app, the system cancels those transfers. That is a product decision too, just not ours.)
Purchase — presentation is not transaction authority

When a server-side billing system is involved, the authority moves again: the client reports, the backend decides. In neither case does a view decide.
Same primitive. Different product semantics..
.task Is Not Wrong
.task is excellent when work genuinely belongs to presentation lifetime.

The mistake is allowing .task to decide ownership for us.
Framework convenience should express an architectural decision, not silently make one.
Separate Presentation From Runtime
Suppose a live session must remain active while the user navigates through related screens.

A longer-lived runtime can own the resource:

Notice what the actor did not do for us. It serializes access, but every await is a suspension point where other work can run. A naive snapshot() that awaited the network and then assigned the result would silently overwrite events the stream applied in the meantime, and two concurrent callers would trigger two fetches. The in-flightsnapshotTask and the event replay exist because of reentrancy, not isolation.
Actors serialize access. They do not serialize intent.
Better — but not finished.
Actors Solve Isolation, Not Ownership
An actor answers an isolation question. It does not automatically answer a lifecycle question.


Who owns SessionRuntime?
If SessionFeature creates and destroys it, we may have moved the original bug down one layer. If the application owns every runtime forever, we have the opposite problem.
If two features request the same session, should we open two streams? Who owns reconnection? What terminates on sign-out?
No actor keyword answers those questions.
Shared Resources Change the Problem
Two presentations may need one underlying resourc

The resource might conceptually behave like:

This is not a prescription to reference-count everything. It demonstrates a principle:
Resource lifetime should be derived from resource semantics.
Structured Concurrency Is Still the Default
When child work really belongs to a parent operation, structure is ideal:


If an upload should survive a view, however, this is the wrong tree:

The problem is not structured concurrency. The problem is structuring work according to implementation convenience instead of product semantics.
Stored Tasks Are an Architectural Signal
Whenever you write:
private var task: Task<Void, Never>?

you should be able to answer:
- Who owns this task?
- What starts it?
- What ends it?
- What does cancellation mean?
- Who observes its result?
- What happens if the owner disappears?
- Can multiple callers start equivalent work?
- What happens on sign-out, backgrounding, or process termination?
If those answers are vague, the task may represent a responsibility that has not been modeled yet.
Since Swift 6.2 you can even write the answer to the first question into the code: Task(name: "Upload \(id)") { ... }. The name shows up when you debug and profile, so the task explains who it belongs to.
Task.detached Should Trigger a Stronger Question
Task.detached has legitimate uses, but the review question should be:
Why must this work be detached from the current task hierarchy?
"Because I don't want cancellation" is not a lifecycle model.
"Because it needs to run in the background" conflates concurrency, isolation, scheduling, application background execution, persistence, and domain lifetime.
If an operation must survive a screen, model an owner whose lifetime survives the screen.
Sendable Is an Architectural Boundary Signal

Compare a value like that with a mutable controller holding a connection, cache, and state.
Perhaps the thing that should cross an isolation boundary is not the controller. Perhaps it is a snapshot, event, command, or identifier.
That makes Sendable more than a compiler hurdle. It can force us to ask what our boundaries actually carry.
@unchecked Sendable may occasionally be justified, but it transfers the proof obligation from the compiler to us.
Did we prove the type is safe to transfer, or did we merely make the diagnostic disappear?
Let Lifetimes Follow Semantics

Navigation may destroy presentation without destroying the runtime. In another feature, such as search suggestions, presentation and operation lifetime may intentionally be almost identical.
The same holds across technology boundaries. In a Flutter app with native modules, the platform channel is an ownership boundary: a Dart widget being disposed should not, by itself, decide whether a native purchase, upload or capture session ends.
There is no universally correct Task lifetime. There is a lifetime that correctly represents the product operation.
@MainActor Is a Boundary, Not a Default Architecture

UI state is often a natural main-actor boundary. But marking an entire dependency graph @MainActor merely to silence diagnostics can turn a useful boundary into accidental architecture.
Ask whether the state is presentation state, whether a resource actually requires main-actor isolation, whether CPU-heavy analysis belongs there, and whether values — not mutable controllers — should cross the boundary.
This matters more since Swift 6.2. New Xcode 26 projects can make MainActor the default isolation for unannotated code (SE-0466), and with Approachable Concurrency, nonisolated async functions run on the caller's actor unless they are marked @concurrent (SE-0461). Those defaults remove a lot of noise. They also make it easy to never decide where the boundary is.
Use @concurrent deliberately for CPU-heavy analysis. Keep the runtime off the main actor because you chose to, not because a diagnostic pushed you there.
Isolation should describe the system. It should not merely appease the compiler.
Failure and Cancellation Are Different States

Even this depends on semantics.
A domain operation explicitly cancelled by the user may need a visible .cancelled state. A superseded search usually should not display "Search cancelled." A resource disappearing unexpectedly may be a failure rather than cancellation.
Architecture should preserve those meanings instead of reducing everything to an Error.
Test the Contract, Not the Task

These tests do not care whether production code stores a particular Task. They test product contracts, leaving the implementation room to evolve.
A Master-Level Code Review Checklist
When reviewing concurrent Swift, do not stop at "Is this thread-safe?"
Ask:
- What product operation does this asynchronous work represent?
- Who owns that operation?
- Does the owner's lifetime match the operation's lifetime?
- What makes the result obsolete?
- What does cancellation mean in this domain?
- Should navigation affect the operation at all?
- Is this resource shared?
- Who closes or evicts it?
- Are actor boundaries modeling isolation or hiding unclear ownership?
- Are Sendable values crossing boundaries, or are mutable responsibilities leaking across them?
- Are we testing task mechanics or product semantics?
- What happens during sign-out, suspension, restoration, and failure?
Those questions expose bugs that perfectly valid Swift syntax cannot.
The Broader Lesson
Swift's concurrency system makes many unsafe programs harder to write.
That is a major improvement.
But compiler-enforced safety cannot choose product semantics for us.
The compiler can help determine whether data may cross an isolation boundary. It cannot decide whether leaving a screen should terminate an upload.
An actor can protect mutable state. It cannot decide who should own the actor.
Structured concurrency can propagate cancellation through a task tree. It cannot decide whether that tree represents the correct product hierarchy.
SwiftUI can tie asynchronous work to presentation lifetime. It cannot decide whether the business operation belongs to that presentation.
Those decisions remain architecture.
And architecture starts by asking what the work means.
Before You Write the Next Task
Draw this first:

Then ask:

Only then choose the Swift mechanism.
Sometimes the answer will be .task.
Sometimes it will be structured child tasks.
Sometimes a feature model should own the task.
Sometimes the operation belongs to an actor-backed runtime or application service.
The goal is not to avoid Task.
The goal is to stop letting Task define architecture accidentally.
Final Thought
We often talk about Swift Concurrency as a problem of execution:
Where does this code run?
Modern Swift increasingly helps us answer that safely.
The harder production question is about ownership:
Who is responsible for this work while it matters?
And beyond ownership lies the most important question:
When does it stop mattering?
That answer rarely comes from the framework.
It comes from the product.
A Task is a unit of execution. It is not necessarily a unit of product work.
Design the lifetime first.
Then write the task.
Swift in Practice — engineering Swift beyond syntax.
Originally published as a Swift in Practice edition on LinkedIn.