Swift: Swiftクロージャ:クロージャ式とトレイリングクロージャ

クロージャはSwiftにおける自己完結型の機能ブロックで、周囲のコンテキストから変数や定数への参照をキャプチャして保存できます。このレッスンではクロージャ式から始め、Swiftで最も柔軟な言語機能の一つをマスターしていきます。

1. 学習目標


2. データアナリストの実話

(1) 課題:再利用できないソートロジックと冗長なコード

Aliceはユーザーデータを名前、年齢、登録日でソートする必要があります。彼女の最初のアプローチは各ソートに個別の関数を書くことでした:

SWIFT
func sortByName(_ a: [String: Any], _ b: [String: Any]) -> Bool {
    return (a["name"] as! String) < (b["name"] as! String)
}
func sortByAge(_ a: [String: Any], _ b: [String: Any]) -> Bool {
    return (a["age"] as! Int) < (b["age"] as! Int)
}

ソートルールが変わるたびに新しい関数を書く必要があり、コードが肥大化します。ソートロジックが複数の関数に散らばっており、呼び出し箇所で動的に指定できません。

(2) クロージャによる解決策

Swiftのクロージャを使えば、ソートルールを sorted(by:) の呼び出し箇所に直接インラインで記述できます:

SWIFT
let sortedByAge = users.sorted { $0["age"] as! Int < $1["age"] as! Int }
let sortedByName = users.sorted { $0["name"] as! String < $1["name"] as! String }

ソートロジックが「関数を書いて→渡す」から「使う場所で定義する」に変わります。

(3) 利点:インライン定義、柔軟な切り替え

観点 独立関数 インラインクロージャ
コード行数 ソートごとに3〜5行 1行
可読性 関数定義を探す必要あり 呼び出し箇所でロジックが見える
柔軟性 全戦略を事前定義必須 その場で定義可能
コンテキストアクセス 外部変数をキャプチャ不可 コンテキストの状態をキャプチャ可能

3. クロージャ式

(1) 基本構文

クロージャ式はクロージャを簡潔に書く方法です。完全な構文は以下の通りです:

100%
graph LR
    A[クロージャ式] --> B["{ (パラメータ) -> 戻り値の型 in"]
    A --> C["    文"]
    A --> D["}"]
    B --> E["{ (a: Int, b: Int) -> Bool in"]
    C --> F["    return a < b"]
    D --> G["}"]
構成要素 必須 備考
{ } はい クロージャは波括弧で囲む
パラメータリスト いいえ 短縮名を使えば省略可能
-> 戻り値の型 いいえ 自動推論可能
in 条件付き パラメータや戻り値の型を宣言する場合は必須

▶ サンプル: クロージャ式の3つの書き方

SWIFT
// ============================================
// Three ways to write closures for sorted(by:)
// ============================================
let numbers = [5, 2, 8, 1, 9]
// Approach 1: Full closure expression
let sorted1 = numbers.sorted(by: { (a: Int, b: Int) -> Bool in
    return a < b
})
// Approach 2: Type inference (omit parameter types and return type)
let sorted2 = numbers.sorted(by: { a, b in return a < b })
// Approach 3: Omit return (single-expression implicit return)
let sorted3 = numbers.sorted(by: { a, b in a < b })
print("sorted3: \(sorted3)")

出力:

TEXT 📖 参照専用
sorted3: [1, 2, 5, 8, 9]

4. トレイリングクロージャと短縮引数名

(1) トレイリングクロージャ

クロージャが関数の最後の引数の場合、括弧の外に移動できます:

スタイル
標準 sorted(by: { a, b in a < b })
トレイリング sorted { a, b in a < b }
複数引数トレイリング animate(duration: 1) { ... }

▶ サンプル: トレイリングクロージャによる呼び出しの簡略化

SWIFT
// ============================================
// Trailing closure concise syntax
// ============================================
let scores = [78, 92, 85, 68, 99]
// Standard syntax
let ascending1 = scores.sorted(by: { a, b in a < b })
// Trailing closure syntax
let ascending2 = scores.sorted { a, b in a < b }
print("Ascending: \(ascending2)")
// Multiple closure parameters also supported
func performRequest(url: String, onSuccess: (String) -> Void, onError: (Error) -> Void) {
    // Simulate network request
    onSuccess("Data received")
}
performRequest(url: "https://api.example.com") { data in
    print("Success: \(data)")
} onError: { error in
    print("Error: \(error)")
}

出力:

TEXT 📖 参照専用
Ascending: [68, 78, 85, 92, 99]
Success: Data received

(2) 短縮引数名

Swiftはインラインクロージャのパラメータに自動的に短縮名 $0$1$2 ... を提供し��す。

完全な構文 短縮形
{ a, b in a < b } { $0 < $1 }
{ name in print(name) } { print($0) }
{ a, b, c in a + b + c } { $0 + $1 + $2 }

▶ サンプル: 短縮引数名の実践

SWIFT
// ============================================
// Step-by-step simplification with shorthand names
// ============================================
let words = ["banana", "apple", "cherry", "date"]
// Full syntax
let sortedA = words.sorted(by: { (a: String, b: String) -> Bool in
    return a < b
})
// Shorthand parameter names — most concise
let sortedB = words.sorted(by: { $0 < $1 })
// Even simpler — pass the operator function directly
let sortedC = words.sorted(by: <)
print(sortedC)

出力:

TEXT 📖 参照専用
["apple", "banana", "cherry", "date"]

よくある落とし穴: 短縮引数名を��う場合、クロージャ本体が単一の式のみであれば return を省略できます。複数の文がある場合は return を明示する必要があります。


5. 値キャプチャ

クロージャは周囲のコンテキストから定数や変数をキャプチャでき、そのコンテキストが消滅した後も保持します。

100%
graph TB
    A[外部関数 makeIncrementer] --> B["変数 total = 0 を宣言"]
    A --> C["クロージャ { total += amount; return total } を返す"]
    C --> D[クロージャが total への参照をキ��プチャ]
    D --> E[makeIncrementer が返った後も]
    E --> F["total はクロージャ内で生き続ける"]

▶ サンプル: クロージャによるコンテキスト変数のキャプチャ

SWIFT
// ============================================
// Value capture — generating an accumulator
// ============================================
func makeIncrementer(step: Int) -> () -> Int {
    var total = 0
    let incrementer: () -> Int = {
        total += step  // Captures total and step
        return total
    }
    return incrementer
}
let incrementByTwo = makeIncrementer(step: 2)
print(incrementByTwo()) // 2
print(incrementByTwo()) // 4
print(incrementByTwo()) // 6

出力:

TEXT 📖 参照専用
2
4
6

ヒント: 値キャプチャはクロージャの最も強力な機能の一つです。Swiftはキャプチャされた変数のメモリを自動管理します — クロージャは自身が解放されるまで、キャプチャした変数への強い参照を保持します。

▶ サンプル: カスタムソートルール

SWIFT
// ============================================
// Custom sorting rules with closures
// ============================================
struct Product {
    let name: String
    let price: Double
    let rating: Int
}
let products = [
    Product(name: "Laptop", price: 1299, rating: 5),
    Product(name: "Mouse", price: 29, rating: 4),
    Product(name: "Keyboard", price: 99, rating: 5),
    Product(name: "Monitor", price: 399, rating: 3)
]
// Sort by price ascending
let byPrice = products.sorted { $0.price < $1.price }
print("Cheapest: \(byPrice.first!.name)")
// Sort by rating descending, then by price ascending on tie
let byRatingThenPrice = products.sorted {
    if $0.rating != $1.rating {
        return $0.rating > $1.rating
    }
    return $0.price < $1.price
}
print("Best value: \(byRatingThenPrice.first!.name)")

出力:

TEXT 📖 参照専用
Cheapest: Mouse
Best value: Keyboard

6. 完全な例:ユーザーリストソーター

SWIFT
// ============================================
// Complete example: User list sorter
// Features: Closure expressions + trailing closures + shorthand names + value capture
// ============================================
import Foundation
// 1. User model
struct User {
    let name: String
    let age: Int
    let score: Double
}
let users = [
    User(name: "Alice", age: 28, score: 92.5),
    User(name: "Bob", age: 22, score: 85.0),
    User(name: "Charlie", age: 35, score: 95.5),
    User(name: "Diana", age: 19, score: 78.0)
]
// 2. Sorter factory — captures sort direction
func makeSorter(ascending: Bool) -> (User, User) -> Bool {
    return { ascending ? $0.score < $1.score : $0.score > $1.score }
}
// 3. Sort using shorthand parameter names
let ascendingSorter = makeSorter(ascending: true)
let sortedAsc = users.sorted(by: ascendingSorter)
print("Ascending:")
for u in sortedAsc {
    print("  \(u.name): \(u.score)")
}
// 4. Inline sort with trailing closure
let sortedDesc = users.sorted { $0.score > $1.score }
print("Descending:")
for u in sortedDesc {
    print("  \(u.name): \(u.score)")
}

出力:

TEXT 📖 参照専用
Ascending:
  Diana: 78.0
  Bob: 85.0
  Alice: 92.5
  Charlie: 95.5
Descending:
  Charlie: 95.5
  Alice: 92.5
  Bob: 85.0
  Diana: 78.0

❓ よくある質問

Q クロージャと関数の違いは何ですか?
A クロージャはより簡潔な構文を持つ無名関数です。関数は名前を持ち繰り返し呼び出せます。クロージャは通常一度限りのシナリオで使われます。クロージャはコン��キスト変数をキャプチャできます — ネスト関数も可能ですが、通常のグローバル関数はできません。
Q in キーワードはいつ書く必要がありますか?
A クロージャ式がパラメータリストや戻り値の型を宣言する場合、宣言と本体を分離するために in を使う必要があります。短縮引数名($0 など)を使い、すべての型注釈を省略する場合は in は不要です。
Q $0$1 の順序はどう決まりますか?
A $0 はクロージャの第1パラメータ、$1 は第2パラメータ、以降同様です。順序はクロージャが呼び出される際の引数渡しの順序で決まり、パラメータ名ではありません。
Q キャプチャされた値はコピーですか、参照ですか?
A クロージャは変数を参照でキャプチャします(inout に似ています)— 複数のクロージャが同じ変数を共有する場合、一方の変更が他方に影響します。定数は値のコピーでキャプチャされます。
Q クロージャが長すぎて可読性が損なわれる場合は?
A クロージャ本体が5〜10行を超える場合は、独立した関数か名前付きクロージャ変数に抽出してから渡します。例:let sorter = { a, b in ... }; sorted(by: sorter)

📖 まとめ


📝 練習問題

  1. 基本: 整数配列 [3, 7, 1, 9, 4, 6] が与えられたとき、sorted(by:) を3つの異なる方法(完全なクロージャ、型推論、短縮引数名)で書き、降順にソートしてください。
  2. 中級: 回数とクロージャを受け取り、指定された回数だけクロージャを実行する関数 repeatTask(times:task:) を書いてください。それを使って "Hello, Swift!" を5回出力してください。
  3. 発展: 演算子文字列(+-*/)を受け取り、対応する (Int, Int) -> Int クロージャを返す makeCalculator 関数を実装してください。その関数を使って加算クロージャと乗算クロージャを作成し、それぞれ (10, 5) を計算してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%