/*:#localized(key: "ClassicVsModernWrappingUp")
 # Wrapping up

 You've now watched one everyday task (fetch two things, combine them, handle
 errors, deliver on main) solved both ways, and the modern version wins on
 readability, safety, and sheer line count. The honest takeaway isn't "rewrite
 everything," though; it's knowing what maps to what, and where migrating
 actually pays. The summary below is a map for both.

 * The modern model wins the same task: no shared mutable state, no
 `enter`/`leave`, one error path, and an automatic main-thread hop via
 `@MainActor`.
 * Every classic tool has a modern counterpart:
   * completion handler ➡️ `async` function
   * `DispatchGroup`/semaphore ➡️ `async let`/`TaskGroup`
   * serial-queue guarding ➡️ **actor**
   * `DispatchQueue.main` ➡️ **`@MainActor`**
   * manual `Operation` cancellation ➡️ cooperative `Task` cancellation
   * delegate/callback ➡️ `AsyncStream`/continuation
 * **Classic still earns its place** for older APIs, fine-grained dispatch
 control, pre-`async`/`await` OS targets, and code that already works: a
 rewrite is a cost, so the trap is spending it where there's no payoff.
 * **Migrate at the seams:** wrap legacy callbacks with continuations, write
 new code modern, replace ad-hoc locks with actors, adopt `@MainActor` for UI,
 and turn on Swift 6 data-race checking incrementally.
 * **Bridge the boundary carefully:** a continuation resumes **exactly once**,
 so a callback that fires many times needs an `AsyncStream`. And never block on
 `DispatchGroup.wait()`/`semaphore.wait()` inside a `Task`: bridge with a
 continuation and `await` it instead.
 * **`@MainActor` doesn't move your slow work**: it guarantees the annotated
 code runs on main. Do heavy work in a plain async function off main, and keep
 the `@MainActor` part to the small UI update at the end.
 * The two models **interoperate**: mixed code is normal and expected during a
 migration.

 ---

 ## Where to go next

 That's the whole book: from a single blocked barista on the main thread to
 actors that keep shared state safe within a single process. You've met the
 classic model (GCD and Operations) and the modern one (`async`/`await`,
 structured concurrency, `AsyncSequence`, and actors), and you've seen *why* the
 second exists as an answer to the first.

 The concepts only truly settle once you use them on something you care about.
 So here's the encouragement to close on: **open one of your own projects and
 revisit the modern chapters**. Find a completion handler and turn it into
 `async`/`await`. Find a shared mutable value guarded by a lock and make it an
 `actor`. Find a screen that stutters and move its heavy work off the main
 thread. Each small change will make one chapter click into place for good.

 Concurrency stops being scary the moment you've made it work, on purpose, in
 code you own. Go do that. You're ready.

 ---

 ## Bonus track: actors across machines

 Curious how far the actor idea stretches? There's one more chapter past this
 one, **[Distributed Actors](Distributed%20Actors.playgroundchapter/Overview)**,
 as extra credit. It carries the same `await`-to-call feel out to objects living
 in *other processes, even on other machines*. It's a read-along concept tour
 rather than a runnable lab, so treat it as a look ahead for when the core ideas
 feel solid.

 [Previous](@previous)  |  page 8 of 8
 */
