关于 swift
面向 Swift 开发的编码技能,侧重类型安全的 API、明确的并发边界和清晰的文件组织,适用于 Apple 平台代码及 Nitro Module。
主要功能
- 01类型安全的状态与 API 设计
- 02明确并发与队列使用方式
- 03设计非阻塞属性
- 04组织实现文件与辅助代码
- 05Nitro Module 实现指导
适用场景
- 01设计状态清晰的 Swift API
- 02选择异步与队列边界
- 03组织 Apple 平台实现文件
- 04实现 Swift Nitro Module
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ margelo/ react-native-skills --skill swift安装后在 Claude Code 中输入/swift确认已加载。
附带文件agents/openai.yaml
详细介绍
Swift
当 Swift 代码需要明确的 API 边界、可预测的线程处理,以及符合 Apple 平台惯例的集成时,使用此技能。如果代码属于 Nitro 模块,请将此技能与 build-nitro-modules 搭配使用,以处理生成的规范、Promise 映射和 HybridObject 约束。
工作流
- 编辑前,先阅读本地 Swift 代码、生成的协议和周边 API 结构。
- 先选择公共类型模型:尽可能用 Swift 类型确保无效状态无法表示。
- 编写实现代码前,先为该功能选择一种并发模型。
- 保持同步属性和方法开销低、作用范围局部且不阻塞。
- 在 API 中明确体现队列切换、硬件/会话协商、I/O 和可能失败的异步工作。
类型安全的 API 设计
- 使用类型表示状态变体,而不是一组可空字段。如果不同变体共享公共契约,可使用协议及其遵循该协议的结构体/类;如果变体集合固定且更像值类型,可使用带关联值的
enum。 - 同一变体中的相关字段应保持为非可选项。如果
barcode和barcodeType只有同时存在才有意义,就将两者放在ScannedBarcode中;不要在通用扫描数据结构体中将两者都设为可选项。 - 仅当同一状态中确实可能缺少某个领域值时才使用可选项,不要用它来表示对象所处的状态。
- 优先采用编译时流程,而不是让调用方自行探查。如果调用方需要反复使用
if let链来确定哪些字段组合有效,那么 API 结构可能不合理。
// Avoid: the valid combinations are implicit and easy to misuse.
struct ScannedData {
let position: Point
let text: String?
let barcode: String?
let barcodeType: BarcodeType?
let face: Rect?
}
// Prefer: each state exposes the fields that are valid for that state.
protocol ScannedData {
var position: Point { get }
}
struct ScannedText: ScannedData {
let position: Point
let text: String
}
struct ScannedBarcode: ScannedData {
let position: Point
let barcode: String
let barcodeType: BarcodeType
}
struct ScannedFace: ScannedData {
let position: Point
let face: Rect
}
并发模型
- 为某项功能选择 Swift 并发或 DispatchQueue,不要将两者交织为控制流。
- 快速、确定性的本地操作应保持同步。对于简单的值构造、缓存元数据或纯转换,不要引入
Task、DispatchQueue或 Promise 流程。 - 仅当整个操作都能在 Swift 并发中清晰表示、无需借助队列变通方案时,才使用 Swift
async/await、Task和 actor。 - 如果 Apple API、委托、回调、C++ 桥接、JS 运行时或 Nitro 线程边界本身已围绕队列运作,或者较重的原生操作不能阻塞调用方,请使用私有且由组件拥有的串行
DispatchQueue。 - 除非平台 API 要求在主线程上运行(例如 UIKit/AppKit/VisionKit 的展示或视图修改),否则避免使用
DispatchQueue.main。主线程代码块应保持简短,并将解析、转换、I/O、会话协商和 CPU 密集型工作移至自有队列或异步 API。 - 将反复切换
Task、DispatchQueue、actor 或线程视为架构异味。组件应自行管理其工作所依赖的队列或 actor,或者只在公共异步边界或原生回调边界跨入该所有者一次。 - 如果工作流在多个嵌套位置反复切换主线程、后台线程、JS 和原生队列,请停止并重新设计对象、生命周期或 API。过多的切换会隐藏延迟、增加推理执行顺序的难度,并造成未来的性能问题。
- 不要使用
DispatchQueue.asyncAfter、Task.sleep、Thread.sleep、计时器、额外的队列切换或重复调用同一方法来修复竞态或就绪状态错误。应修复所有者队列、生命周期状态、完成回调、委托事件或异步 API 边界。仅针对硬件、OS 服务、远程服务或网络等外部不确定性进行重试,并确保重试次数有限、可取消且幂等。 - 不要在非隔离或 Nitro 生成的入口点中使用
Task { @MainActor in ... }作为通用的主线程切换方式。它会创建非结构化的 Swift 并发任务。只有当该操作本身基于 Swift 并发,且端到端使用async/await、任务取消或 actor 隔离确有裨益时,才使用它。 - 对于 UIKit、AppKit、VisionKit 和其他在主线程回调或委托的 API,优先使用直接的
DispatchQueue.main.async边界,或由异步公共方法负责切换。Swift 的 actor 检查器能识别直接的DispatchQueue.main.async对@MainActor调用的切换;通用队列包装器(如Promise.parallel(.main))通常无法识别。 - 如果回调可能在任意队列上返回,应在靠近回调源的位置将其统一切换到选定的所有者队列一次,而不是在工作流中散布嵌套的
Task { @MainActor }或DispatchQueue.main.async切换。 - 不要强行使用
MainActor.assumeIsolated、Thread.isMainThread分支或队列.sync调用来让 Swift 并发设计通过编译。这表明应选择基于队列的设计,或更改 API 边界。 - 不要调用
DispatchQueue.sync、DispatchQueue.main.sync或等效的同步队列切换。应将其视为错误,尤其是在属性读取器和设置器中。 - 对于调用方必须等待的队列绑定工作,请使用
DispatchQueue.async或 NitroPromise方法。 - 将锁视为最后手段,而非默认的安全包装。在添加
NSLock之前,应明确具体的共享可变值、可能并发访问它们的线程或队列,以及为何由MainActor、串行队列、Nitro 运行时/线程或不可变快照管理所有权不足以解决问题。 - Swift
Array和Dictionary的修改不是线程安全的。如果监听器的添加或移除可能与原生委托的事件发送发生竞态,应在同一所有者队列/线程上串行处理所有访问,或仅用一个小型锁来修改并快照监听器注册表。 - 持锁期间绝不要调用回调、调用 JS/Nitro 或调用未知用户代码。在锁内快照监听器,然后解锁再调用。正在进行的事件发送期间被移除的监听器仍可能收到当前事件;这是可接受的清理语义,不足以成为采用更复杂锁机制的理由。
属性与线程
- 属性仅用于成本低廉、可立即安全读写的观测状态。
- 不要通过属性隐藏线程切换、硬件/会话查询、锁等待、权限检查、内存分配或可能失败的原生操作。
- 如果某个值只能从特定队列中观测到,优先使用从该队列发出的事件/监听器。
- 如果调用方确实需要从另一个队列中读取一次值,请公开异步 getter 方法,而不是属性。
- 在 Nitro Modules 中,将受队列约束的读写表示为
Promise<T>方法,通常使用Promise.parallel(queue)。
// Avoid: synchronous queue hop hidden in a getter.
var status: SessionStatus {
queue.sync { session.status }
}
// Prefer: make the async boundary explicit.
func getStatus() throws -> Promise<SessionStatus> {
return Promise.parallel(queue) {
return self.session.status
}
}
Swift 风格
- 除非设计要求支持子类,否则默认将类声明为
final。 - 优先使用
String、Array、Dictionary、结构体、协议和 Foundation 值类型等 Swift 原生类型。除非 Apple API 要求,否则避免使用 Objective-C 桥接类型。 - 使用
guard尽早验证输入和状态。对于用户可能遇到的失败,抛出具体错误。 - 将文件名视为作用域约定。
HybridDataScanner.swift应实现HybridDataScanner,不应同时包含扩展、转换、UIViewController辅助代码、委托或框架适配器。 - 每个文件只保留一个顶层实现类型。不要在主要类型下方添加辅助结构体、类、枚举、协议、选项适配器、协调器、委托或辅助类型。如果某个辅助项值得成为一种类型,它就值得拥有自己的文件。
- 每个文件只放一个专注的扩展或转换。不要在一个工具文件中收集多个扩展。将可复用扩展放入命名文件,例如
Extensions/UIViewController+topPresentedViewController.swift、Extensions/CGPoint+Point.swift或Extensions/Barcode+toScannedCode.swift,并在适当情况下使用internal或package可见性。 - 绝不要将 Swift 扩展放在
Hybrid*实现文件或其他主要实现文件中,即使扩展是私有的、很短,或仅由该文件使用。将每个扩展都放在单独命名的Type+operation.swift扩展/转换文件中,以保持代码拆分、可维护性和未来差异的整洁。 - 将委托、框架适配器、转换器、原生协议和辅助状态拆分到单独文件中,不要将它们添加到主类型下方。
- 将原生预检和平台样板代码从
Hybrid*工厂和实现方法中提取出来。权限/状态切换、Bundle.main.object(forInfoDictionaryKey:)检查、硬件能力检查、Info.plist 验证以及类似的设置守卫,都应放在专注的辅助/扩展文件中,例如AVCaptureDevice+CameraAuthorization.swift或Bundle+CameraUsageDescription.swift;工厂调用处应保持为一两行。 Hybrid*Factory.swift只负责协调:解析生成的选项、调用预检辅助项、创建/启动原生会话,并返回/拒绝 Promise。不要在工厂文件中定义会话/协调器/委托类、Native*Options适配器、呈现控制器遍历辅助项、扫描器配置构建器、条码映射、权限切换或 Info.plist 检查。- 如果某个
Hybrid*方法开始包含 Apple 授权/状态切换、用于平台设置的多行guard、视图控制器查找逻辑、DataScannerViewController配置或转换循环,就应先将其提取出来再继续。调用方应呈现为一连串简短的命名操作,而不是每项操作的实现过程。 - 不要为这些辅助项创建宽泛的
Utils.swift文件。根据平台类型或领域检查命名文件,并让每个辅助项专注于一项检查或转换。 - 将行数作为审查信号:只有在满足每文件一个类型且工厂中没有辅助代码这两项规则后,约 300 行以内的文件通常才算合适。因扩展、转换、辅助类型或平台胶水代码而导致文件过大,是设计问题。
- 对含义明确的单表达式闭包,优先使用内联简写:
targetFormats.flatMap { $0.toVNBarcodeFormat() },而不是多行的flatMap { format in format.toVNBarcodeFormat() }。如果外层或嵌套闭包已使用简写参数,不要使用$0;在嵌套闭包中,或为了表达清晰时,应为参数命名。 - 如果转换只读取单个元素,就将转换放在元素类型上,而不是数组上。元素方法仍可返回多个值;调用方可使用
map、flatMap、reduce或Set(...)进行组合。 - 除非转换确实属于
Int、String、Double、Any、CGPoint或CGRect等宽泛/常见接收类型,否则不要在这些类型上添加领域转换。优先采用领域类型方向,例如BarcodeFormat.from(format:)或BarcodeFormat(nativeFormat:),而不是Int.toBarcodeFormat()。 - 只有当集合具备真正的领域行为时,才添加
Array或Collection扩展,例如跨元素验证、去重、排序、分批、缓存、非空检查或错误汇总。如果接收者是具体的[SomeDomainType],且主体代码主要只是省去一个map,就将该map留在调用方代码中。 - 将有意义的转换拆分为命名的中间值,避免使用冗长的内联表达式。
Nitro 提示
- 对于基于 DispatchQueue 的工作(例如 AVFoundation 会话操作),请使用
Promise.parallel(queue)。 - 仅当需要端到端封装 Swift
async/await或原生支持 Task 的 API 时,才使用Promise.async。 - 避免手动创建或传递
Promise<T>实例。优先使用Promise.parallel、Promise.async、Promise.resolved和Promise.rejected;只有在处理真正的原生完成处理、委托或回调桥接时才手动创建 Promise,并将其作用域限制在最小范围内,确保只完成一次。 - 不要将
Promise.async与队列.sync调用或 Actor 逃逸机制混用。 - 生成的 HybridObject 属性是同步入口。如果需要队列亲和性,请将它们重新设计为方法或监听器。
评论
支持 Markdown