mcpField
S
swift

swift

github.com/margelo开发179734 次安装10 周前更新
代码仓库

关于 swift

面向 Swift 开发的编码技能,侧重类型安全的 API、明确的并发边界和清晰的文件组织,适用于 Apple 平台代码及 Nitro Module。

主要功能

  1. 01类型安全的状态与 API 设计
  2. 02明确并发与队列使用方式
  3. 03设计非阻塞属性
  4. 04组织实现文件与辅助代码
  5. 05Nitro Module 实现指导

适用场景

  1. 01设计状态清晰的 Swift API
  2. 02选择异步与队列边界
  3. 03组织 Apple 平台实现文件
  4. 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 约束。

工作流

  1. 编辑前,先阅读本地 Swift 代码、生成的协议和周边 API 结构。
  2. 先选择公共类型模型:尽可能用 Swift 类型确保无效状态无法表示。
  3. 编写实现代码前,先为该功能选择一种并发模型。
  4. 保持同步属性和方法开销低、作用范围局部且不阻塞。
  5. 在 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 或 Nitro Promise 方法。
  • 将锁视为最后手段,而非默认的安全包装。在添加 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