swift-language について
Swift コードのモダナイズとレビューを支援する言語スキルです。現行の構文とパターンを扱い、動作と評価順序の維持を重視し、専門領域は関連スキルに委ねます。
主な機能
- 01if・switch 式
- 02Typed throws と結果ビルダー
- 03プロパティラッパーと型の選択
- 04guard パターンと Never の意味
- 05正規表現ビルダーとコレクション API
- 06モダナイズのレビューチェックリスト
活用シーン
- 01動作を変えずに条件分岐をモダナイズ
- 02some と any の型選択
- 03Swift コードのよくある問題のレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill swift-languageインストール後、Claude Code で/swift-languageを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/swift-attributes-interop.mdswift-patterns-extended.md詳細
Swift 言語パターン
動作や評価順序を変えずに、現在の Swift 言語構文を適用します。
複雑なデコードは swift-codable、書式設定は swift-formatstyle、命名は
swift-api-design-guidelines、並行処理は swift-concurrency、SwiftUI の
状態やビューに関する作業は swiftui-patterns に任せます。
目次
- If/Switch 式
- 型付き throws
- 結果ビルダー
- プロパティラッパー
- 不透明型と存在型
- Guard パターン
- Never 型
- 正規表現ビルダー
- Codable のベストプラクティス
- 最新のコレクション API
- FormatStyle
- 文字列補間
- よくある間違い
- レビュー用チェックリスト
- 参考資料
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 を参照してください。
よくある間違い
someで済む場面でanyを使う。 戻り値の型とパラメーターには、まずsomeを使います。ただし、すべての-> some P分岐で同じ具象型を返す必要があります。- コレクション API の代わりに手動ループや
.filter { }.countを使う。 条件付きの件数にはcount(where:)を使い、余分な反復や配列を避けるためにcontains(where:)、compactMap、flatMapも使います。 - FormatStyle の代わりに
DateFormatterを使う。.formatted()はよりシンプルで型安全であり、ローカライズも自動的に処理します。 - Codable のデコードで強制アンラップする。 オプションのキーや欠落しているキーには、デフォルト値とともに
decodeIfPresentを使います。 - モダナイズの際に事前条件の順序を変更する。
guardを使い、検証より前に正規化や変換を移動しないでください。 - 無効な
@cシグネチャを使う。UnsafeBufferPointerは Swift の構造体/値ラッパーだと説明したうえで、String、Array、クロージャ、汎用プレースホルダーを使用しないようにします。 - 型付きスローを無視する。 関数のエラー型が明確に 1 つだけの場合、型付きスローを使うと、呼び出し元はキャストせずに網羅的な switch を記述できます。
- プロパティラッパーを使いすぎる。 再利用や射影値が不要なら、計算プロパティのほうがシンプルです。
Neverの説明が不十分。Result<T, Never>またはthrows(Never)については、注意点を明示してください。Never は任意のプロトコルに暗黙的に適合せず、任意のT: P制約を満たすこともできません。また、ボトム型に似た性質を持つのは、有効な式/型推論の文脈に限られます。- 関連する実装まで担当する。 担当するスキル名を示して、そこで止めます。
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 は関連するスキルに振り分けている
参考資料
- 拡張パターンと Codable の例:references/swift-patterns-extended.md
- 属性と C との相互運用:references/swift-attributes-interop.md