swift について
型安全なAPI、明確な並行処理の境界、整理されたファイル構成を重視するSwift開発向けスキルです。AppleプラットフォームのコードやNitro Moduleを扱う開発者を対象としています。
主な機能
- 01型安全な状態とAPIの設計
- 02並行処理とキューの明確化
- 03ノンブロッキングなプロパティ設計
- 04実装ファイルとヘルパーの整理
- 05Nitro Moduleの実装指針
活用シーン
- 01状態が明確なSwift APIの設計
- 02非同期処理とキュー境界の選定
- 03Appleプラットフォーム実装ファイルの整理
- 04Swift Nitro Moduleの実装
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ margelo/ react-native-skills --skill swiftインストール後、Claude Code で/swiftを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yaml
詳細
Swift
強固な API 境界、予測可能なスレッド処理、慣用的な Apple プラットフォームとの統合が必要な Swift コードには、このスキルを使用してください。コードが Nitro Module の一部である場合は、生成された仕様、Promise のマッピング、HybridObject の制約に対応するため、build-nitro-modules と組み合わせてください。
ワークフロー
- 編集する前に、ローカルの Swift コード、生成されたプロトコル、周辺の API の形を確認します。
- まず公開型のモデルを選びます。Swift で表現できる場合は、無効な状態を表現できないようにします。
- 実装コードを書く前に、その機能で使用する並行処理モデルを 1 つ選びます。
- 同期プロパティとメソッドは、軽量で、ローカルに完結し、ノンブロッキングに保ちます。
- キューの切り替え、ハードウェアやセッションのネゴシエーション、I/O、失敗する可能性のある非同期処理は、API で明示します。
型安全な API 設計
- 状態のバリエーションは、null 許容のフィールド群ではなく型で表現します。バリエーションが公開契約を共有する場合は、プロトコルと、それに準拠する構造体やクラスを使用します。バリエーションが固定され、値として扱える場合は、関連値を持つ
enumを使用します。 - 関連するフィールドは、同じバリエーション内で非オプショナルに保ちます。
barcodeとbarcodeTypeが組み合わせでのみ意味を持つ場合は、両方をScannedBarcodeに含めます。汎用のスキャンデータ構造体で、両方をオプショナルにしないでください。 - オプショナルは、1 つの状態における実際のドメイン上の不在にのみ使用し、オブジェクトの状態を表すためには使用しません。
- 呼び出し側での判定を繰り返すのではなく、コンパイル時にフローを決定できる設計を優先します。有効なフィールドの組み合わせを見つけるために、呼び出し側で
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、アクターを使用してください。 - Apple の API、デリゲート、コールバック、C++ ブリッジ、JS ランタイム、Nitro のスレッド境界がすでにキューを中心に動作している場合や、負荷の高いネイティブ処理で呼び出し元をブロックしてはならない場合は、専用のプライベートなシリアル
DispatchQueueを使用してください。 - UIKit/AppKit/VisionKit での表示やビューの変更など、プラットフォーム API がメインスレッドを必要とする場合を除き、
DispatchQueue.mainは避けてください。メインスレッド上のブロックは小さく保ち、解析、変換、I/O、セッションネゴシエーション、CPU 処理は専用キューまたは非同期 API に移してください。 Task、DispatchQueue、アクター、スレッドの間を何度も行き来するのは、アーキテクチャ上の問題の兆候として扱ってください。コンポーネントは、自身が使用するキュー/アクターを所有するか、公開非同期境界またはネイティブコールバック境界で一度だけ所有者へ移行するようにしてください。- ワークフローが複数のネストした場所でメイン、バックグラウンド、JS、ネイティブの各キューを行き来する場合は、いったん止めて、オブジェクト/ライフサイクル/API を再設計してください。過剰な切り替えはレイテンシーを隠し、順序の把握を難しくし、将来的なパフォーマンスの問題を引き起こします。
- 競合や準備完了状態の不具合を、
DispatchQueue.asyncAfter、Task.sleep、Thread.sleep、タイマー、キューの追加切り替え、同じメソッドの二重呼び出しで修正しないでください。代わりに、所有キュー、ライフサイクル状態、完了コールバック、デリゲートイベント、または非同期 API 境界を修正してください。ハードウェア、OS サービス、リモートサービス、ネットワークなど、外部の非決定性がある場合に限って再試行し、回数を制限し、キャンセル可能かつ冪等にしてください。 - 非隔離または Nitro 生成のエントリーポイントからメインスレッドへ移行する汎用的な手段として、
Task { @MainActor in ... }を使用しないでください。これは非構造化の Swift 並行処理を作成します。処理がほかの点でも Swift の並行処理に基づいており、最初から最後までasync/await、タスクのキャンセル、またはアクターによる分離を活用できる場合に限り使用してください。 - UIKit、AppKit、VisionKit などのメインスレッド用コールバック/デリゲート API では、直接
DispatchQueue.main.asyncを使って境界を設けるか、移行を担う非同期公開メソッドを優先してください。直接のDispatchQueue.main.asyncは@MainActorの呼び出しについて Swift のアクターチェッカーに認識されますが、Promise.parallel(.main)のような汎用キューラッパーは通常認識されません。 - コールバックが任意のキューで返る可能性がある場合は、ネストした
Task { @MainActor }やDispatchQueue.main.asyncをワークフロー全体に散在させるのではなく、コールバックの発生元に近い場所で一度だけ、選択した所有キューへ切り替えてください。 - Swift の並行処理による設計をコンパイルするために、
MainActor.assumeIsolated、Thread.isMainThreadの分岐、キューの.sync呼び出しを強制しないでください。これは、キューベースの設計を選ぶか、API 境界を変更すべき兆候です。 DispatchQueue.sync、DispatchQueue.main.sync、または同等の同期的なキュー切り替えを呼び出さないでください。特にプロパティのゲッターとセッターでは、バグとして扱ってください。- 呼び出し元が完了を待つ必要があるキュー依存の処理には、
DispatchQueue.asyncまたは Nitro のPromiseメソッドを使用してください。 - ロックは最終手段の同期プリミティブとして扱い、デフォルトの安全対策として使用しないでください。
NSLockを追加する前に、具体的な共有可変値、それらへ同時アクセスする可能性のあるスレッド/キュー、そしてMainActor、シリアルキュー、Nitro ランタイム/スレッド、または不変スナップショットによる所有では不十分な理由を特定してください。 - Swift の
ArrayとDictionaryの変更はスレッドセーフではありません。リスナーの追加/削除とネイティブデリゲートのイベント送出が競合する可能性がある場合は、すべてのアクセスを単一の所有キュー/スレッドで直列化するか、リスナーの登録簿の変更とスナップショット作成に限って小さなロックを使用してください。 - コールバックの実行中、JS/Nitro の呼び出し中、または不明なユーザーコードの呼び出し中にロックを保持しないでください。ロック中にリスナーのスナップショットを取得し、ロックを解除してから呼び出してください。処理中のイベント送出時に削除されたリスナーが、そのイベントを受け取る場合があります。これは許容されるクリーンアップ動作であり、より複雑なロックを正当化する理由にはなりません。
プロパティとスレッド処理
- プロパティは、すぐに安全に読み書きできる、コストの低い観測状態にのみ使用してください。
- スレッド切り替え、ハードウェアやセッションへの問い合わせ、ロック待機、権限チェック、メモリ確保、失敗する可能性のあるネイティブ操作を、プロパティの背後に隠さないでください。
- 特定のキューからしか値を観測できない場合は、そのキューから発行されるイベントやリスナーを優先してください。
- 呼び出し元が別のキューから一度だけ読み取る必要がある場合は、プロパティではなく、非同期ゲッターメソッドを公開してください。
- 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ヘルパー、デリゲート、フレームワークアダプターを併せて含めてはいけません。 - ファイルごとに、トップレベルの実装型を1つだけにします。プライマリ型の下に、補助的な構造体、クラス、列挙型、プロトコル、オプションアダプター、コーディネーター、デリゲート、ヘルパー型を置いてはいけません。ヘルパーを型にする価値があるなら、専用のファイルを用意します。
- ファイルごとに、目的を絞った拡張または変換を1つだけ置きます。1つのユーティリティファイルに複数の拡張をまとめてはいけません。再利用可能な拡張は、適切な場合には
internalまたはpackageの可視性を設定し、Extensions/UIViewController+topPresentedViewController.swift、Extensions/CGPoint+Point.swift、Extensions/Barcode+toScannedCode.swiftのような名前付きファイルに置きます。 - 拡張が
privateであっても、小規模であっても、そのファイルだけで使われるものであっても、Swift の拡張をHybrid*実装ファイルやその他のプライマリ実装ファイルに置いてはいけません。コード分割、保守性、将来の差分を整理しやすく保つため、すべての拡張を個別のType+operation.swift拡張・変換ファイルに置きます。 - デリゲート、フレームワークアダプター、コンバーター、ネイティブプロトコル、補助状態は、メインの型の下に追加するのではなく、別々のファイルに分割します。
- ネイティブの事前確認とプラットフォームの定型処理は、
Hybrid*ファクトリーや実装メソッドから切り出します。権限・状態の切り替え、Bundle.main.object(forInfoDictionaryKey:)の確認、ハードウェア機能の確認、Info.plist の検証、および同様のセットアップガードは、AVCaptureDevice+CameraAuthorization.swiftやBundle+CameraUsageDescription.swiftのような、目的を絞ったヘルパー・拡張ファイルに置きます。ファクトリーの呼び出し箇所は1、2行に保ちます。 Hybrid*Factory.swiftはオーケストレーションのみにします。生成されたオプションの解決、事前確認ヘルパーの呼び出し、ネイティブセッションの作成・開始、Promise の返却または拒否を行います。セッション/コーディネーター/デリゲートのクラス、Native*Optionsアダプター、プレゼンターの走査ヘルパー、スキャナー構成ビルダー、バーコードのマッピング、権限の切り替え、Info.plist の確認をファクトリーファイルに定義してはいけません。Hybrid*メソッドに、Apple の認可・状態を切り替えるswitch、プラットフォームのセットアップを行う複数行のguard、ビューコントローラーの検索ロジック、DataScannerViewControllerの構成、変換ループが増えてきたら、それ以上進める前に切り出します。呼び出し元は各処理の実装そのものではなく、名前付き操作を短く並べたものとして読めるようにします。- これらのヘルパー用に、範囲の広い
Utils.swiftファイルを作成してはいけません。プラットフォームの型または対象の確認内容にちなんだファイル名を付け、各ヘルパーの目的を1つに絞ります。 - 行数はレビューの目安として使います。およそ300行未満のファイルは、1ファイル1型の原則とファクトリーにヘルパーを置かないルールを満たしたうえであれば、通常は問題ありません。拡張、変換、ヘルパー型、プラットフォーム固有の処理によってファイルが大きくなっている場合は、設計上の問題です。
- 意味が明確な単一式のクロージャでは、インラインの省略記法を優先します。複数行の
flatMap { format in format.toVNBarcodeFormat() }ではなく、targetFormats.flatMap { $0.toVNBarcodeFormat() }とします。周囲または入れ子のクロージャですでに省略引数を使っている場合は$0を使わず、入れ子のクロージャでは引数に名前を付けるか、明確さが必要な場合は名前付き引数を使います。 - 変換が1要素だけを読み取る場合は、配列ではなく要素型に変換を定義します。要素のメソッドが複数の値を返してもかまいません。呼び出し元では
map、flatMap、reduce、Set(...)を使って組み合わせます。 - その型自体に関わる変換でない限り、
Int、String、Double、Any、CGPoint、CGRectなど、広範または一般的なレシーバーにドメイン変換を定義してはいけません。Int.toBarcodeFormat()よりも、BarcodeFormat.from(format:)やBarcodeFormat(nativeFormat:)のように、ドメイン型を起点とする方向を優先します。 - コレクション全体にわたる検証、重複排除、順序付け、バッチ処理、キャッシュ、空でないことの確認、エラーの集約など、コレクションに実質的なドメイン動作がある場合にのみ、
ArrayまたはCollectionの拡張を追加します。レシーバーが具体的な[SomeDomainType]で、本文の大部分が1つのmapを省略するだけなら、そのmapは呼び出し元のコードに残します。 - 意味のある変換は、長いインライン式にせず、名前付きの中間値に分けます。
Nitro に関する注意事項
- AVFoundation セッション操作など、DispatchQueue ベースの処理には
Promise.parallel(queue)を使用してください。 - Swift の
async/awaitまたは Task ネイティブの API を一貫してラップする場合にのみ、Promise.asyncを使用してください。 Promise<T>インスタンスを手動で作成したり、受け渡したりするのは避けてください。Promise.parallel、Promise.async、Promise.resolved、Promise.rejectedを優先してください。手動で Promise を使うのは、ネイティブの完了通知/デリゲート/コールバックとのブリッジが実際に必要な場合に限り、スコープを最小限にして、完了が一度だけ実行されるようにしてください。Promise.asyncとキューの.sync呼び出し、またはアクターの制約を回避する方法を併用しないでください。- 生成された HybridObject のプロパティは、同期的なエントリーポイントです。キューへの親和性が必要な場合は、メソッドまたはリスナーとして設計し直してください。
コメント
Markdown 対応