$npx -y skills add margelo/react-native-skills --skill swiftDesign, implement, and review Swift APIs and Apple-platform code. Use when working on .swift files, Swift types, Foundation or AVFoundation APIs, DispatchQueue, async/await, Task, actors, MainActor, thread-affine state, or Swift-backed React Native Nitro Module implementations.
| 1 | # Swift |
| 2 | |
| 3 | Use this skill for Swift code that needs strong API boundaries, predictable threading, and idiomatic Apple-platform integration. When the code is part of a Nitro Module, pair this with `build-nitro-modules` for generated specs, Promise mapping, and HybridObject constraints. |
| 4 | |
| 5 | ## Workflow |
| 6 | |
| 7 | 1. Read the local Swift code, generated protocols, and surrounding API shape before editing. |
| 8 | 2. Choose the public type model first: make invalid states unrepresentable where Swift can express them. |
| 9 | 3. Choose one concurrency model for the feature before writing implementation code. |
| 10 | 4. Keep synchronous properties and methods cheap, local, and nonblocking. |
| 11 | 5. Make queue hops, hardware/session negotiation, I/O, and fallible async work explicit in the API. |
| 12 | |
| 13 | ## Type-Safe API Design |
| 14 | |
| 15 | - Represent state variants with types, not nullable clusters. Use protocols plus conforming structs/classes when variants share a public contract, or use `enum` with associated values when the set is closed and value-like. |
| 16 | - Keep related fields nonoptional on the same variant. If `barcode` and `barcodeType` are meaningful only together, put both on `ScannedBarcode`; do not make both optional on a generic scanned-data struct. |
| 17 | - Use optionals only for real domain absence inside one state, not for expressing which state the object is in. |
| 18 | - Prefer compile-time flow over caller-side probing. If callers need repeated `if let` chains to discover valid field combinations, the API shape is probably wrong. |
| 19 | |
| 20 | ```swift |
| 21 | // Avoid: the valid combinations are implicit and easy to misuse. |
| 22 | struct ScannedData { |
| 23 | let position: Point |
| 24 | let text: String? |
| 25 | let barcode: String? |
| 26 | let barcodeType: BarcodeType? |
| 27 | let face: Rect? |
| 28 | } |
| 29 | |
| 30 | // Prefer: each state exposes the fields that are valid for that state. |
| 31 | protocol ScannedData { |
| 32 | var position: Point { get } |
| 33 | } |
| 34 | |
| 35 | struct ScannedText: ScannedData { |
| 36 | let position: Point |
| 37 | let text: String |
| 38 | } |
| 39 | |
| 40 | struct ScannedBarcode: ScannedData { |
| 41 | let position: Point |
| 42 | let barcode: String |
| 43 | let barcodeType: BarcodeType |
| 44 | } |
| 45 | |
| 46 | struct ScannedFace: ScannedData { |
| 47 | let position: Point |
| 48 | let face: Rect |
| 49 | } |
| 50 | ``` |
| 51 | |
| 52 | ## Concurrency Model |
| 53 | |
| 54 | - Choose Swift concurrency or DispatchQueue for a feature, not both as interleaved control flow. |
| 55 | - Keep quick, deterministic, local work synchronous. Do not introduce `Task`, `DispatchQueue`, or Promise plumbing for simple value construction, cached metadata, or pure transforms. |
| 56 | - Use Swift `async`/`await`, `Task`, and actors only when the full operation can be represented cleanly in Swift concurrency without queue escape hatches. |
| 57 | - Use a private owned serial `DispatchQueue` when Apple APIs, delegates, callbacks, C++ bridges, JS runtimes, or Nitro thread boundaries already revolve around queues, or when heavier native work must not block the caller. |
| 58 | - Avoid `DispatchQueue.main` unless the platform API requires the main thread, such as UIKit/AppKit/VisionKit presentation or view mutation. Keep main-thread blocks small and move parsing, conversion, I/O, session negotiation, and CPU work to an owned queue or async API. |
| 59 | - Treat repeated `Task`, `DispatchQueue`, actor, or thread hops as an architecture smell. A component should either own the queue/actor it works on, or cross into that owner once at the public async boundary or native callback boundary. |
| 60 | - If a workflow bounces between main, background, JS, and native queues in multiple nested places, stop and redesign the object/lifecycle/API. Excessive hops hide latency, make ordering harder to reason about, and create future performance problems. |
| 61 | - Do not fix races or readiness bugs with `DispatchQueue.asyncAfter`, `Task.sleep`, `Thread.sleep`, timers, extra queue hops, or calling the same method twice. Fix the owner queue, lifecycle state, completion callback, delegate event, or async API boundary instead. Retry only for external nondeterminism such as hardware, OS services, remote services, or network, and keep retries bounded, cancellable, and idempotent. |
| 62 | - Do not use `Task { @MainActor in ... }` as a generic main-thread hop from a nonisolated or Nitro-generated entry point. It creates unstructured Swift concurrency. Use it only when the operation is otherwise Swift-concurrency based and benefits from `async`/`await`, task cancellation, or actor isolation end to end. |
| 63 | - For UIKit, AppKit, VisionKit, and other main-thread callback/delegate APIs, prefer a direct `DispatchQueue.main.async` boundary or an async public method that owns the hop. Direct `DispatchQueue.main.async` is understood by Swift's actor checker for `@MainActor` calls; generic queue wrappers such as `Promise.parallel(.main)` usually are not. |
| 64 | - If a callback can return on an arbitrary queue, normalize it |