Swift: Swiftクロージャ:クロージャ式とトレイリングクロージャ
クロージャはSwiftにおける自己完結型の機能ブロックで、周囲のコンテキストから変数や定数への参照をキャプチャして保存できます。このレッスンではクロージャ式から始め、Swiftで最も柔軟な言語機能の一つをマスターしていきます。
1. 学習目標
- クロージャ式の構文
- トレイリングクロージャのルール
$0、$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) 基本構文
クロージャ式はクロージャを簡潔に書く方法です。完全な構文は以下の通りです:
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. 値キャプチャ
クロージャは周囲のコンテキストから定数や変数をキャプチャでき、そのコンテキストが消滅した後も保持します。
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)。📖 まとめ
- クロージャ式は
{ }で囲み、パラメータと戻り値の型をinキーワードの前に宣言する - トレイリングクロージャは最後の引数の括弧を省略でき、呼び出しがより自然になる
- 短縮引数名
$0、$1、$2によりクロージャ記述が大幅に簡略化される - 単一式のクロージャは
returnキーワードを省略できる(暗黙的return) - クロージャは周囲のコンテキストから変数や定数をキャプチャして変更できる
sorted(by:)はクロージャの最も一般的なユースケースの一つ
📝 練習問題
- 基本: 整数配列
[3, 7, 1, 9, 4, 6]が与えられたとき、sorted(by:)を3つの異なる方法(完全なクロージャ、型推論、短縮引数名)で書き、降順にソートしてください。 - 中級: 回数とクロージャを受け取り、指定された回数だけクロージャを実行する関数
repeatTask(times:task:)を書いてください。それを使って "Hello, Swift!" を5回出力してください。 - 発展: 演算子文字列(
+、-、*、/)を受け取り、対応する(Int, Int) -> Intクロージャを返すmakeCalculator関数を実装してください。その関数を使って加算クロージャと乗算クロージャを作成し、それぞれ(10, 5)を計算してください。