本技能提供符合语言习惯、生产级质量的 Swift 6 编程知识,假定读者已掌握语法,重点放在容易出错的地方,如协议扩展派发、actor 重入、Sendable 逃逸口、continuation 误用、Codable 默认值以及两个共享 swift test 命令的测试框架。
【工具链】SwiftPM 是构建工具和包管理器。Xcode 项目使用 xcodebuild 构建;即使应用封装了库,也应将库保留在 SwiftPM 包中,以便在 Linux 和 CI 上无需模拟器即可构建和测试。常用命令包括:新建包、添加依赖、将依赖关联到目标、解析/更新依赖、带测试构建、运行测试、列出测试 ID、发布构建、格式化及 lint。工具版本行(如 swift-tools-version: 6.0)同时选择清单 API 和 Swift 6 语言模式。应用程序和可执行文件应提交 Package.resolved;库可以省略,由使用者自行解析依赖图。迁移旧目标时,可通过 swiftSettings 保留 Swift 5 模式并启用 StrictConcurrency 作为警告,修复后移除。可执行文件应保持精简,逻辑放在库目标中以便测试。推荐的 CI 门禁包括严格格式化 lint、带警告视为错误的构建以及并行测试。
【值类型与写时复制】默认使用 struct 和 enum;当身份或共享可变状态是重点时使用 class。struct 复制独立变化,消除别名错误,并且当所有存储属性都是 Sendable 时自动获得 Sendable 一致性。struct 持有类引用时复制的是引用,值语义在该字段处终止。Array、Dictionary、Set 和 String 是写时复制的:复制是 O(1),首次修改共享缓冲区时才会复制。自定义包装类存储的 struct 需自行实现写时复制(使用 isKnownUniquelyReferenced)。对同一变量的重叠 inout 访问在编译期可见时报错,不可见时运行时陷阱。
【可选值与错误】使用 if let、guard let、?? 和可选链解包;guard 保持成功路径不缩进。!、try! 和 as! 仅用于程序自身保证的事实;外部数据(环境、文件、网络、用户输入)永远不是这种事实。失败应建模为遵循 Error 的枚举并携带关联值。try? 丢弃原因,仅在原因无关紧要时使用。Swift 6 支持类型化 throws,适合模块内部代码和封闭错误集;公共 API 应保留非类型化 throws,因为类型化错误集是签名的一部分,无法在不破坏调用者的情况下扩展。precondition 和 fatalError 在 -O 下保留;assert 在发布构建中被移除,绝不能用 assert 验证输入。
【协议与泛型】some P 是编译期选择的单一具体类型:静态派发、特化、无装箱。any P 是存在类型盒子,动态派发,可能堆分配。默认使用 some P 或泛型参数;异构存储时使用 any P。主要关联类型可约束两种形式。条件一致性通过扩展实现。为不属于自己的类型添加不属于自己的协议一致性在 Swift 6 中会警告;仅在接受所有者未来可能添加相同一致性时使用 @retroactive。注意:只有协议要求的方法才动态派发;仅在协议扩展中定义的方法按静态类型选择,遵循类型的同名方法通过 any P 或泛型 T: P 会被忽略。
【结构化并发】async let 并发运行固定数量的子任务;withThrowingTaskGroup 运行动态数量的子任务。作用域因错误退出时,剩余子任务被取消并等待。取消是协作式的,应在循环中检查 Task.checkCancellation();Task.sleep 抛出 CancellationError。非结构化 Task { } 继承调用者的 actor 和优先级但不继承取消;Task.detached 不继承任何内容。应保留每个非结构化任务的句柄,并在其所有者消失时取消。Actor 序列化对其状态的访问,但它们是可重入的:每次 await 时其他调用可能运行并改变状态,因此每次挂起后需重新检查不变量。@MainActor 隔离 UI 状态和视图模型。Sendable:成员为 Sendable 的 struct 和 enum 在模块内隐式遵循;类在 final 且仅有 let Sendable 属性时,或由锁保护状态并标记 @unchecked Sendable 并注释锁名称时遵循。Swift 6 语言模式将数据竞争诊断变为错误。@preconcurrency import 可抑制尚未采用 Sendable 的模块的诊断。Swift 6.2 增加了按目标设置 .defaultIsolation(MainActor.self) 和 NonisolatedNonsendingByDefault 特性。桥接回调 API 时,检查型 continuation 必须在每条路径上恰好恢复一次;恢复两次会陷阱,永不恢复会泄漏任务并记录 SWIFT TASK CONTINUATION MISUSE。
【模块与访问控制】每个目标是一个模块。internal(默认)模块内可见;public 暴露 API;open 还允许模块外子类和重写;package 跨同一包的模块;fileprivate 和 private 进一步收窄。测试使用 @testable import 访问 internal 符号,不要为测试放宽访问权限。资源通过目标的 resources 声明,并通过 Bundle.module 读取。对来自其他模块的枚举使用 @unknown default 可在枚举新增 case 时保持编译并警告。
【SwiftUI 要点】视图是值,SwiftUI 会频繁重建;保持 body 轻量且无副作用。状态所有权:@State 用于视图拥有的值和视图创建的 @Observable 模型;@Binding 用于子视图写入父值;@Bindable 用于进入 @Observable 模型的绑定;@Environment 用于注入的共享对象。iOS 17 之前仍使用 @StateObject 和 @ObservedObject 处理 ObservableObject。.task { } 运行与视图生命周期绑定的异步工作,并在视图消失时取消;.task(id:) 在 id 变化时重启。优先使用它而非 onAppear { Task { } },后者从不取消。
【测试】两个框架在一个 swift test 下运行:XCTest 和 Swift Testing(6.0 工具链)。声明方式、断言、设置、参数化、并行性各有不同。Swift Testing 默认并行,可用 .serialized 特质退出。异步 XCTest 使用 async throws 和 await fulfillment;wait(for:) 标记为 noasync。应注入时钟和文件系统根,而不是休眠或触碰真实路径。可运行单个测试或跳过。
【Loom 测试运行器适配器】适配器为 swift-test。包含 Package.swift 的目录是 Swift 包,Loom 检测始终为其选择 swift-test。loom project detect 打印每个包的 kind、runner 和 skill。仅含 Xcode 项目而没有 Package.swift 的不会被检测为 swift。单测试命令为 swift test --filter '{test}',测试字段格式为 Module.Class/testMethod,其中 Module 是测试目标的模块名。swift test list 打印此格式的 ID。注意:捕获运行器夹具的主机上未安装 swift,因此 Loom 的 swift-test 解析器是根据运行器文档化输出编写的;不要依赖退出码,Loom 读取 XCTest 摘要中的 Executed N tests, with F failures;Executed 0 tests 表示未选中任何测试,因此名称不匹配的契约测试会导致冻结失败。