iOS / Swift / Editorial solution guide

Diagnose stale tasks and session results

Reproduce out-of-order actor completions without confusing cancellation, view lifetime, and current account ownership.

About 3 min readContent reviewed

Read-only knowledge base ยท No public comments

Recognize the symptoms

A search briefly displays the right result, then an older response replaces it. An obsolete failure can also cover a newer success.

The model is main-actor isolated, and task cancellation is requested on input changes, yet the defect remains. A related case publishes previous-account data after logout and login.

Understand the likely causes

  • Actor isolation prevents unsynchronized state access but does not make a workflow atomic across await. Cancellation is cooperative and can arrive after useful work already completed.
  • Publication paths may guard success but leave error handling or busy-state cleanup unguarded. A long-lived model or cache can also outlive its original session.

Capture the event order with safe identities

Build a synthetic reproduction with request A and request B, and record start, completion, cancellation observation, and publication as different events. Include a session generation if account ownership can change. Avoid logging tokens or complete private search text. A completing before B is not a requirement of starting first, so a trace showing reversed completion is normal. The failure is a commit that no longer belongs to current inputs. Also inspect whether a SwiftUI task restarts because its id changes or because the containing view identity changes unexpectedly.

Declare which operation may publish

For live search, use a latest-input policy with a request ticket captured before await. If the view can survive an account switch, capture session identity too. A completion may publish only when both still match current ownership. Apply the same gate to errors and pending-state transitions. An obsolete request must not clear the loading indicator for a newer request. Keep cancellation as a resource optimization and lifecycle signal, while the identity gate remains the local correctness boundary. Transactional saves need a different policy because the server may already have accepted an older command.

Test the publication gate independently

The fragment demonstrates the decisive comparison without creating a task or contacting a service. Matching request numbers are insufficient when the session differs. In the real model, advance identity under the same owner that starts work or changes accounts, then compare immediately before each state mutation. Do not store a mutable global current account inside a fake and read it only at completion, because that can erase which account actually started the request. Capture ownership at start and preserve it through the operation.

swift Illustrative Swift or SwiftUI fragment; requires the declared toolchain host.
struct ResultOwner: Equatable, Sendable {
    let session: Int
    let request: Int
}
let started = ResultOwner(session: 1, request: 4)
let current = ResultOwner(session: 2, request: 4)
print(started == current)
Expected output
false

The pure equality result rejects a previous-session result. It does not cancel a task or prove service authorization.

Reproduce both successful and failing stale completions

Use a fake repository with explicit pending responses. Start A, start B, complete B successfully, then complete A successfully and verify B remains visible. Repeat with A throwing, then with logout and a new session before A completes. Add a case where the view disappears while the model intentionally survives, and verify the documented ownership policy rather than assuming disappearance always destroys the model. Controlled continuations must each resume exactly once, including cleanup after a failed test. No wall-clock sleep or live endpoint is required to exercise this ordering.

Verify the fix

The newest result survives stale success and stale failure; old work cannot clear current pending state; session changes prevent old private publication and cache notifications.

Report fake model checks separately from actual SwiftUI task cancellation, URLSession transport, and Apple runtime behavior. None of those SDK integrations were executed in the authoring environment.

Keep it from recurring

  • Keep request freshness, session scope, cancellation, actor isolation, and server authorization as explicit contracts. Preserve the deferred-response regression fixture and review every success, catch, and pending-state path when changing asynchronous code.

Official references