Sendable and Swift 6 strict checking

Concurrency Senior Swift 6.0+ iOS 17+ Reviewed By Alok Singh

Pick the fix

Swift 6 rejects Task.detached { cache.store[url] = data } because ImageCache isn't Sendable. The cache is shared app-wide. Best fix?

final class ImageCache {
    var store: [URL: Data] = [:]
}
let cache = ImageCache()
Task.detached { cache.store[url] = data }
Options
Hint 1 of 2

Is the state shared and mutable?

Hint 2 of 2

Which options only silence the compiler?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Make ImageCache an actor and await cache.insert(data, for: url)

Correct. Shared mutable state belongs in an actor, which makes access safe.

Why not the others?
  • B Mark the class @unchecked Sendable. It compiles, but nothing protects store; two tasks writing at once is still a data race.
  • C Change ImageCache to a struct. Each task would get its own copy, so it is no longer a shared cache, and you can't mutate a captured let.
  • D Declare nonisolated(unsafe) let cache. This opts out of checking for that variable; the race remains.

30-second answer

Sendable marks types that are safe to share across concurrency domains. Value types made of Sendable parts, actors, and final classes with only immutable Sendable state qualify. In the Swift 6 language mode, the compiler rejects sending non-Sendable values across actors, which turns data races into compile errors.

Practise saying it · 60 s. No recording, just a timer

Explain Sendable and how you fix Swift 6 errors.

Say your answer out loud, then check which of these points you covered:

  • Sendable = safe to share across domains
  • Prefer values and actors
  • @unchecked is a manual promise
  • Migrate module by module

Deep dive

Fix options, best first: use value types; make shared mutable state an actor; keep the value in one isolation domain; use sending for one-time transfers.

@unchecked Sendable is a promise you keep yourself, usually with a lock. Use it rarely and document it.

@preconcurrency import quiets warnings from modules that aren't annotated yet.

Closures passed across actors must be @Sendable and capture only Sendable values.

Common mistakes

  • Adding @unchecked Sendable to silence errors.
  • Turning on Swift 6 mode for a whole app at once instead of module by module.
  • Thinking structs are always Sendable even when they hold classes.

Senior take & follow-ups

Seniors describe a migration plan and prefer restructuring ownership over annotations.

Follow-up questions

Think of your answer first, then open the hint.

  1. What does the sending parameter keyword do?

    Show hint

    It lets a non-Sendable value cross once, if the caller never uses it again.

  2. How do you migrate a big app?

    Show hint

    Turn on complete checking as warnings, module by module, then switch the language mode.

Suggest a fix if something here is wrong or out of date.