SuperAPI AI 工具集

write-swift

编程与开发

查看项目

更新于 10-27 16:26

技能介绍

write-swift 是一个指导如何以 Swift 语言期望的方式编写现代 Swift 代码的技能,内容更新至 Swift 6.4。其工具链基线为 Swift 6.3(资料标注为 2026 年 8 月的当前版本),文中内容除标注 ⚠ 的未发布 Swift 6.4 特性外均可在 6.3 上编译;并发指导基于 Swift 6.2 模型,若项目使用 6.1 或更早版本,则关于 async 与 @concurrent 的规则不适用。

核心思路:Swift 是一门渐进式披露的语言。从最简单、最静态、最单线程且可行的方案开始,只有在能指出明确理由时才引入动态特性——并发、引用语义、存在类型、不安全指针。文中所有规则都是这一理念的应用。

默认层级:数据默认用 struct / enum,只有需要身份、共享或继承时才用 class;抽象默认用具体类型,只有跨类型出现重复代码时才抽象;多态默认用 some P(泛型),只有需要异构存储时才用 any P;执行默认在主 actor 上同步进行,只有性能分析显示卡顿才依次升级到 async、@concurrent、actor;内存默认用 Array、String,只有性能分析显示成本过高才用 InlineArray、Span;安全默认用安全 API,只有 C 互操作或实测热路径才用 Unsafe*。

主要能力与内容分段:

一、用值类型建模数据。默认使用 struct 和 enum,class 仅用于身份、共享可变状态、继承或资源生命周期。默认用 let,仅在需要修改时用 var。含可变引用类型属性的 struct 既非值也非引用,需保持被引用类型不可变、只暴露转发计算属性,或将其作为私有存储属性配合写时复制。写时复制通过将 final class 包装进 struct 并在修改前检查 isKnownUniquelyReferenced(&storage) 实现,Array、String、Dictionary 正是如此。enum 用于固定集合与互斥状态,用单一 enum State 替代多个可选存储属性可使非法组合不可表示、状态变更原子化。非可复制类型(~Copyable)表达唯一所有权,将“不得运行两次”从断言变为编译错误,并让 struct 的 deinit 有意义;参数所有权显式化:borrowing(只读,默认)、consuming(取走)、inout/mutating(临时写访问)。

二、错误与可选值。可恢复错误用 throw,程序员错误用 precondition/fatalError。带关联值的 enum 最适合做错误类型。错误条件用 guard 强制退出路径,普通解包用 if let。类型化 throws(throws(MyError))适用于内部函数、错误转发泛型代码和装箱 any Error 成本过高的受限环境;公共 API 用非类型化 throws 以保留日后更改错误类型的自由。throws 即 throws(any Error),非抛出即 throws(Never)。仅在能说明不变式时强制解包,优先使用带消息的 #require/precondition 而非裸 !。

三、并发:在性能分析表明必要之前保持单线程。每个应用都从完全在主线程开始。升级顺序为:主 actor 上单线程 → async/await 隐藏延迟 → @concurrent 将昂贵工作移出主线程(仅在 Instruments 显示卡顿后)→ actor 将状态移出主 actor(仅当主 actor 状态过多导致任务频繁跳转时)。先开启正确构建设置:启用 Approachable Concurrency;应用模块和面向 UI 的模块将 Default Actor Isolation 设为 MainActor(Xcode 26 新应用项目默认如此),包中写作 swiftSettings: [.defaultIsolation(MainActor.self)];通用库不应设为主 actor 默认。Swift 6.2 起,将函数标记为 async 不会使其离开当前 actor,而是在调用处运行;@concurrent 总是切换到并发线程池,用于 CPU 密集工作;nonisolated 在调用处运行,是库 API 的合适默认;两者都不加则留在调用者的 actor 上。先分析再卸载,能用非并发方式提速就先那样做。不要为琐碎工作创建任务。每个端到端操作一个任务。await 是挂起点,会破坏原子性,每次 await 后需重新检查假设,不得跨 await 持有锁或依赖线程本地存储。Actor 保证互斥而非事务:在同步方法中修改 actor 状态,保持异步 actor 方法精简,在每个 await 处让 actor 处于一致状态;actor 不是 FIFO,需要顺序时用任务或 AsyncStream。

四、Sendable 与数据共享。Sendable 标记类型可安全跨隔离域共享,编译器在每个任务和 actor 边界检查。非公开类型的值类型在存储满足时自动推断 Sendable;公开类型永不自动推断。actor 和 @MainActor 类隐式 Sendable。多数模型类应既非 @MainActor 也非 Sendable,需要离开主 actor 时用 nonisolated 而非 Sendable。非 Sendable 对象只要发送方停止使用仍可跨域传递。仅当函数类型确实跨域时才标 @Sendable。@unchecked Sendable 保留给有真实内部同步的类型。遇到数据竞争错误时依次尝试:不共享、改为 Sendable 值类型、隔离到 actor,最后才用 Mutex/Atomic 或 @unchecked Sendable。全局与静态变量是最常见错误来源,优先级为 let、@MainActor、Mutex、nonisolated(unsafe)。桥接旧回调 API 时可用 @MainActor 标注自有委托协议,否则用 nonisolated 加 MainActor.assumeIsolated { },@preconcurrency 是同一做法的简写。

五、结构化并发。优先使用结构化任务:async let 用于固定数量的并发子任务;withTaskGroup 用于动态数量,withDiscardingTaskGroup 用于子任务无返回值的情况;Task { } 仅用于生命周期不适合作用域的工作;Task.detached 几乎不用。取消是协作式的,需检查 Task.isCancelled 或 try Task.checkCancellation(),对挂起中的工作使用 withTaskCancellationHandler。限制并发数量,不要对无界列表每项派生一个子任务。@TaskLocal 沿任务树传播上下文。桥接回调用 withCheckedContinuation / withCheckedThrowingContinuation,契约是每条路径恰好 resume 一次。AsyncSequence 用 for await / for try await 迭代,用 AsyncStream / AsyncThrowingStream 适配既有 API。

六、SwiftUI 中的并发。View 及其包含的 @State 都是 @MainActor 隔离的,几乎不需要写 @MainActor。SwiftUI 会有意在主线程外运行部分代码,信号是 API 签名中的 @Sendable(如 visualEffect、Shape.path(in:)、Layout 要求、onGeometryChange),遇到隔离错误时不要发送 self,而是把所需值复制进闭包捕获列表。SwiftUI 的动作回调刻意同步,时间敏感的 UI 更新应在同一帧完成,仅对后续长时间工作开启 Task。把状态放在 UI 与异步工作的接缝处。

七、协议与泛型。不要从 class 开始,也不要从协议开始。流程是:写具体类型 → 发现跨类型重复代码 → 将共享能力提取为协议 → 针对协议写泛型代码。没有按类型定制的协议是浪费的协议。优先 has-a 而非 is-a,若协议只有部分操作对类型有意义,不要细化协议,而应包装进泛型 struct 并只暴露真正需要的 API。

使用方式:该技能首次被调用且没有具体问题时,只回复“I'm ready to help you write modern Swift, the way the language wants to be written.”,在用户提问前不提供其他信息。

适用场景:编写或审查现代 Swift 代码,尤其是涉及值类型建模、Swift 6 数据竞争安全、并发模型选择、Sendable 处理、结构化并发与 SwiftUI 并发等主题时。

限制:资料未提供该技能的安装方式、收费、价格、平台支持或效果保证等信息;文中标注 ⚠ 的 Swift 6.4 特性尚未发布;并发规则仅适用于 Swift 6.2 及以上的模型。