mcpField
S
swift-language

swift-language

github.com/dpearson2699開発1.2k4.0k 回インストール13 週間前に更新
リポジトリ

swift-language について

Swift コードのモダナイズとレビューを支援する言語スキルです。現行の構文とパターンを扱い、動作と評価順序の維持を重視し、専門領域は関連スキルに委ねます。

主な機能

  1. 01if・switch 式
  2. 02Typed throws と結果ビルダー
  3. 03プロパティラッパーと型の選択
  4. 04guard パターンと Never の意味
  5. 05正規表現ビルダーとコレクション API
  6. 06モダナイズのレビューチェックリスト

活用シーン

  1. 01動作を変えずに条件分岐をモダナイズ
  2. 02some と any の型選択
  3. 03Swift コードのよくある問題のレビュー

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-language

インストール後、Claude Code で/swift-languageを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
swift-attributes-interop.md
swift-patterns-extended.md

詳細

Swift 言語パターン

動作や評価順序を変えずに、現在の Swift 言語構文を適用します。 複雑なデコードは swift-codable、書式設定は swift-formatstyle、命名は swift-api-design-guidelines、並行処理は swift-concurrency、SwiftUI の 状態やビューに関する作業は swiftui-patterns に任せます。

目次

If/Switch 式

モダナイズする際は、現在の動作と評価順序を固定し、意味を変えない書き換えを 1 つ行い、影響を受けるモジュールをコンパイルして、対象を絞ったフィクスチャやテストを実行します。 変更があれば先に修正し、動作が維持されるまで繰り返します。

Swift 5.9 以降では、値を返す式として if と switch を使用できます。 値の代入、返却、初期化に直接使用します。

// Assign from if expression
let icon = if isComplete { "checkmark.circle.fill" } else { "circle" }

// Assign from switch expression
let label = switch status {
case .draft: "Draft"
case .published: "Published"
case .archived: "Archived"
}

// Works in return position
func badgeText(for priority: Priority) -> String {
    switch priority {
    case .high: "High"
    case .medium: "Medium"
    case .low: "Low"
    }
}

ルール:

  • すべての分岐で同じ型の値を生成する必要があります。
  • 複数の文からなる分岐は使用できません。各分岐は 1 つの式である必要があります。
  • 関数の引数として使用する場合は、曖昧さを避けるために括弧で囲みます。

型付き throws

Swift 6 以降では、関数がスローするエラーの型を指定できます。

enum ValidationError: Error {
    case tooShort, invalidCharacters, alreadyTaken
}

func validate(username: String) throws(ValidationError) -> String {
    guard username.count >= 3 else { throw .tooShort }
    guard username.allSatisfy(\.isLetterOrDigit) else { throw .invalidCharacters }
    return username.lowercased()
}

// Caller gets typed error -- no cast needed
do {
    let name = try validate(username: input)
} catch {
    // error is ValidationError, not any Error
    switch error {
    case .tooShort: print("Too short")
    case .invalidCharacters: print("Invalid characters")
    case .alreadyTaken: print("Taken")
    }
}

ルール:

  • 呼び出し側で網羅的なエラー処理ができる利点がある場合に限り、throws(SomeError) を使用します。複数のエラー発生元がある場合は、型を指定しない throws を使用します。
  • ローカルのエラー列挙型が 1 つだけあるヘルパーをモダナイズする場合は、throws(ErrorEnum) を優先し、Swift 6 以降が必要であることを記載します。
  • throws(Never) は、構文上はエラーをスローするものの、実際には決してスローしない関数を示します。ジェネリックなコンテキストで役立ちます。
  • 型付き throws は伝播します。throws(A) と throws(B) を呼び出す関数は、その両方を包含する型を自身のエラー型として指定する必要があります(または型を指定しない throws を使用します)。

結果ビルダー

@resultBuilder を使うと、DSL 形式の構文を実現できます。最も一般的な例は SwiftUI の @ViewBuilder ですが、どのような領域にもカスタムビルダーを作成できます。

@resultBuilder
struct ArrayBuilder<Element> {
    static func buildBlock(_ components: [Element]...) -> [Element] {
        components.flatMap { $0 }
    }
    static func buildExpression(_ expression: Element) -> [Element] { [expression] }
    static func buildOptional(_ component: [Element]?) -> [Element] { component ?? [] }
    static func buildEither(first component: [Element]) -> [Element] { component }
    static func buildEither(second component: [Element]) -> [Element] { component }
    static func buildArray(_ components: [[Element]]) -> [Element] { components.flatMap { $0 } }
}

func makeItems(@ArrayBuilder<String> content: () -> [String]) -> [String] { content() }

let items = makeItems {
    "Always included"
    if showExtra { "Conditional" }
    for name in names { name.uppercased() }
}

ビルダーのメソッド: buildBlock(文を結合)、buildExpression(単一の値)、buildOptional(else のない if)、buildEither(if/else)、buildArray(for..in)、buildFinalResult(任意の後処理)。

プロパティラッパー

カスタム @propertyWrapper 型は、ストレージやアクセスのパターンをカプセル化します。

@propertyWrapper
struct Clamped<Value: Comparable> {
    private var value: Value
    let range: ClosedRange<Value>

    var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    var projectedValue: ClosedRange<Value> { range }

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}

// Usage
struct Volume {
    @Clamped(0...100) var level: Int = 50
}

var v = Volume()
v.level = 150   // clamped to 100
print(v.$level) // projected value: 0...100

設計ルール:

  • wrappedValue は、主要なゲッター/セッターです。
  • projectedValue($property でアクセス)は、メタデータやバインディングを提供します。
  • プロパティラッパーは合成できます。@A @B var x は、外側のラッパーから適用します。
  • 単純な計算プロパティで十分な場合は、プロパティラッパーを使用しないでください。

不透明型と存在型

some Protocol(不透明型)

呼び出し側は具体的な型を知りませんが、コンパイラーは把握しています。-> some P を返す場合、すべての戻り値の分岐で、基盤となる具体的な型は 1 つに固定されます。

func makeCollection() -> some Collection<Int> {
    [1, 2, 3]  // Always returns Array<Int> -- compiler knows the concrete type
}

次の場合に some を使用します。

  • 実装を隠しつつ、型の同一性を保ちたい戻り値の型。
  • パラメーターの型(Swift 5.7 以降):some P は、<T: P> のような名前のないジェネリックパラメーターの省略形です。
any Protocol(存在型)

実行時に、適合する任意の型を保持できる存在型ボックスです。動的ディスパッチを使用し、値がインラインバッファに収まらない場合は、メモリーを割り当てることがあります。

func process(items: [any StringProtocol]) {
    for item in items {
        print(item.uppercased())
    }
}
選択の目安
some を使う場合any を使う場合
具象型を隠す戻り値の型異種コレクション
関数のパラメーター(単純なジェネリクスの代わり)動的な型消去が必要な場合
パフォーマンスの向上(静的ディスパッチ)消去する必要がある Self または関連型の要件をプロトコルが持つ場合

経験則: まずは some を使います。異種コレクションまたは実行時の型の柔軟性が必要な場合に限り、any を使います。

Guard パターン

guard は前提条件を強制し、早期終了を可能にします。正常系の処理を左揃えに保ち、ネストを減らせます。

func processOrder(_ order: Order?) throws -> Receipt {
    // Unwrap optionals
    guard let order else { throw OrderError.missing }

    // Validate conditions
    guard order.items.isEmpty == false else { throw OrderError.empty }
    guard order.total > 0 else { throw OrderError.invalidTotal }

    // Boolean checks
    guard order.isPaid else { throw OrderError.unpaid }

    // Pattern matching
    guard case .confirmed(let date) = order.status else {
        throw OrderError.notConfirmed
    }

    return Receipt(order: order, confirmedAt: date)
}

ベストプラクティス:

  • 前提条件には guard を、分岐ロジックには if を使います。
  • 関連するガードをまとめます:guard let a, let b else { return }。
  • else ブロックではスコープを終了する必要があります:return、throw、continue、break、または fatalError()。
  • 簡略化したアンラップを使います:guard let value else { ... }(Swift 5.7 以降)。

Never 型

Never は、値を生成しないコードパスに使う空の型です。値式を使用または推論できる箇所に限り、Swift のボトム型のように振る舞います。万能な型証明子ではなく、任意のプロトコルに暗黙的に準拠することもありません。また、その制約が Never に対して別途有効でない限り、T: P のようなジェネリック制約を満たすこともできません。

// Function that terminates the program
func crashWithDiagnostics(_ message: String) -> Never {
    let diagnostics = gatherDiagnostics()
    logger.critical("\(message): \(diagnostics)")
    fatalError(message)
}

enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}
// Result<String, Never> -- a result that can never fail

// Exhaustive switch: no default needed since Never has no cases
func handle(_ result: Result<String, Never>) {
    switch result {
    case .success(let value): print(value)
    // No .failure case needed -- compiler knows it's impossible
    }
}

正規表現ビルダー

Swift 5.7 以降の正規表現ビルダー DSL では、コンパイル時に検証される読みやすいパターンを記述できます。

import Foundation
import RegexBuilder

// Parse "2024-03-15" into components
let dateRegex = Regex {
    Capture { /\d{4}/ }; "-"; Capture { /\d{2}/ }; "-"; Capture { /\d{2}/ }
}

if let match = "2024-03-15".firstMatch(of: dateRegex) {
    let (_, year, month, day) = match.output
    _ = (year, month, day)
}

// TryCapture with transform
let priceRegex = Regex {
    "$"
    TryCapture { OneOrMore(.digit); "."; Repeat(.digit, count: 2) }
        transform: { Decimal(string: String($0)) }
}

ビルダーとリテラルの使い分け:

  • ビルダー:複雑なパターン、再利用可能なコンポーネント、キャプチャの強い型付け。
  • リテラル(/pattern/):単純なパターン、正規表現構文に慣れている場合。
  • 両方を組み合わせることもできます:ビルダーブロック内に /.../ リテラルを埋め込みます。

Codable のベストプラクティス

単純な名前変更には CodingKeys を使い、カスタムデコードはペイロードの形状または変換に実際の不一致がある場合に限って使います。簡潔な言語の例については Swift の拡張パターンを読み、実装と検証には swift-codable を使います。

最新のコレクション API

手動のループよりも、次の最新 API を優先してください。

let numbers = [1, 2, 3, 4, 5, 6, 7, 8]

// count(where:) -- use instead of .filter { }.count
let evenCount = numbers.count(where: { $0.isMultiple(of: 2) })

// contains(where:) -- short-circuits on first match
let hasNegative = numbers.contains(where: { $0 < 0 })

// first(where:) / last(where:)
let firstEven = numbers.first(where: { $0.isMultiple(of: 2) })

// String replacing() -- Swift 5.7+, returns new string
let cleaned = rawText.replacing(/\s+/, with: " ")
let snakeCase = name.replacing("_", with: " ")

// compactMap -- unwrap optionals from a transform
let ids = strings.compactMap { Int($0) }

// flatMap -- flatten nested collections
let allTags = articles.flatMap(\.tags)

// Dictionary(grouping:by:)
let byCategory = Dictionary(grouping: items, by: \.category)

// reduce(into:) -- efficient accumulation
let freq = words.reduce(into: [:]) { counts, word in
    counts[word, default: 0] += 1
}

FormatStyle

基本的な表示には .formatted() と Text(_:format:) を使います。スタイルの選択、解析、ローカライズのテスト、再利用可能なフォーマッターの設計については swift-formatstyle を利用してください。

文字列補間

ドメイン固有の書式設定には DefaultStringInterpolation を拡張します。複数行文字列には """ を使います(インデントは閉じる """ を基準にします)。カスタム補間の例については、references/swift-patterns-extended.md を参照してください。

よくある間違い

  1. some で済む場面で any を使う。 戻り値の型とパラメーターには、まず some を使います。ただし、すべての -> some P 分岐で同じ具象型を返す必要があります。
  2. コレクション API の代わりに手動ループや .filter { }.count を使う。 条件付きの件数には count(where:) を使い、余分な反復や配列を避けるために contains(where:)、compactMap、flatMap も使います。
  3. FormatStyle の代わりに DateFormatter を使う。 .formatted() はよりシンプルで型安全であり、ローカライズも自動的に処理します。
  4. Codable のデコードで強制アンラップする。 オプションのキーや欠落しているキーには、デフォルト値とともに decodeIfPresent を使います。
  5. モダナイズの際に事前条件の順序を変更する。 guard を使い、検証より前に正規化や変換を移動しないでください。
  6. 無効な @c シグネチャを使う。 UnsafeBufferPointer は Swift の構造体/値ラッパーだと説明したうえで、String、Array、クロージャ、汎用プレースホルダーを使用しないようにします。
  7. 型付きスローを無視する。 関数のエラー型が明確に 1 つだけの場合、型付きスローを使うと、呼び出し元はキャストせずに網羅的な switch を記述できます。
  8. プロパティラッパーを使いすぎる。 再利用や射影値が不要なら、計算プロパティのほうがシンプルです。
  9. Never の説明が不十分。 Result<T, Never> または throws(Never) については、注意点を明示してください。Never は任意のプロトコルに暗黙的に適合せず、任意の T: P 制約を満たすこともできません。また、ボトム型に似た性質を持つのは、有効な式/型推論の文脈に限られます。
  10. 関連する実装まで担当する。 担当するスキル名を示して、そこで止めます。CodingKeys、デコーダー、フォーマッター、SwiftUI、並行処理のコード例は避けてください。

レビューチェックリスト

  • some は、すべての不透明な戻り値の分岐が 1 つの具象型を持つ場合にのみ使用している
  • 事前条件には guard を使用し、手動のカウントや .filter { }.count の代わりに count(where:) を使用している
  • DateFormatter/NumberFormatter の代わりに .formatted() を使用している
  • Codable 型では API マッピングに CodingKeys を使用し、オプションのフィールドにはデフォルト値とともに decodeIfPresent を使用している
  • 条件付き代入には if/switch 式を使用し、プロパティラッパーには明確な再利用上の理由がある
  • 複雑なパターンには Regex ビルダーを使用している(単純なものにはリテラルでも可)
  • 単一のローカルなエラードメインには型付きスローを使用し、Swift 6 以降との互換性に触れている
  • @c の修正では、UnsafeBufferPointer を Swift の構造体/値ラッパーと説明し、使用できない Swift 専用型を名前で列挙している
  • Never の説明では「inhabited でない」と「ボトム型に似た性質」を用い、任意のプロトコル/ジェネリックへの暗黙的な適合がないことを述べている
  • 深い Codable の内容は swift-codable に、FormatStyle API は swift-formatstyle に、市場/ローカライズ表示の QA は ios-localization に振り分け、命名/並行処理/SwiftUI は関連するスキルに振り分けている

参考資料

コメント