/* Page prose (zh-Hans) */
"ClassicModernInPractice" = "# 经典与现代的实践\n\n## 概念图\n\n几乎每一个经典工具都有一个直接的现代对应物。当你读旧代码，或者迁移它时，这就是那张翻译表：\n\n* 一个 completion handler，`(Result<T,E>) -> Void`，➡️ 一个 `async throws -> T` 函数。结果*作为*返回值回来；错误则 `throw`。\n* `DispatchQueue.global().async { }` ➡️ `Task { }` / 协作线程池。你描述的是*工作*，而不是*哪个队列*。\n* 用于一批工作的 `DispatchGroup` / `semaphore.wait()` ➡️ `async let`（固定的一组）或 `TaskGroup`（动态的一组）。汇合是结构化的；没有要平衡的计数器。\n* 用来守护共享状态的串行队列 ➡️ 一个 **`actor`**。隔离由类型强制执行，而不是靠自律。\n* `DispatchQueue.main.async { }` ➡️ **`@MainActor`**。属于主线程这件事是签名的一部分。\n* 带手动 `isCancelled` 检查的 `Operation` ➡️ 协作式的 **`Task` 取消**（`Task.isCancelled` / `try Task.checkCancellation()`）。取消会自动沿着任务树向下传播。\n* 一个触发很多次的 delegate 或回调 ➡️ **`AsyncStream`**（或用于单次的 **continuation**）。事件变成一个你可以 `break` 出来的 `for await` 循环。\n\n有两点值得强调：\n\n第一，**`async let` 和 `TaskGroup` 都替代了 `DispatchGroup`**：当你事先知道有哪些获取时（就像我们这里的两个）选 `async let`，当数量在运行时才决定时选 `TaskGroup`。\n\n第二，**actor 是那个安静的英雄**：经典模型里最容易出 bug 的东西（一个串行队列或一个计数为 1 的信号量，守护着你必须记得让每一次访问都经过它的共享状态）变成了一个由编译器替你监管的类型。\n\n---\n\n## 什么时候仍然使用经典模型\n\n现代模型是新代码的默认选择。但“经典已死”会是错的，还会把你引向不必要的重写。在以下情况下，去用（或保留）GCD 和 `Operation`：\n\n* **你在调用更老的 API。**  \n很多框架（以及你自己更老的代码）仍然给你 completion handler 或 delegate。你和它们*互操作*；你不必先把整个世界都转换掉。（干净的做法是把它们包起来；见下一页。）\n* **你需要非常精细的 dispatch 控制。**  \n一个带精确 QoS 的特定自定义串行队列，一个用于文件或定时器的 `DispatchSource`，一个并发队列上的屏障块。这些仍然是合理的，有时也是最直接的工具。协作线程池故意隐藏了线程细节；当你真的需要那些细节时，GCD 就在那里。\n* **你面向早于 `async`/`await` 的操作系统版本。**  \nSwift 并发运行在 iOS 13 及以后（以及对应的 macOS 10.15、tvOS 13、watchOS 6）。由于 iOS 13 在 2019 年发布，这个理由现在很少见，但如果一个部署目标仍然低于那条线，GCD 就是你的可移植选项。\n* **代码已经能工作，而你还没有迁移它。**  \n能工作的 GCD 不是 bug。仅仅为了现代化而重写稳定的代码，会在没有面向用户的收益的情况下增加风险。在你本来就在改动的地方迁移，而不是一次性到处都改。\n\n对此保持诚实很重要：目标是*正确、可维护*的代码，而不是为了现代化本身而做到 100% 现代的代码库。\n\n[上一页](@previous)  |  第 5 / 8 页  |  [下一页: 一套务实的迁移策略](@next)";
