Swiftを学習していると必ず出会う「クロージャ」。最初は難しく感じるかもしれませんが、理解すればコードがグッと読みやすく、書きやすくなります。
この記事では、Swift初心者の方でも理解できるように、クロージャの基本から実践的な使い方まで、サンプルコードを交えながら丁寧に解説します。
クロージャとは?
クロージャとは、処理をひとまとめにしたコードブロックのことです。変数に代入したり、関数の引数として渡したりできます。
他のプログラミング言語では「ラムダ式」や「無名関数」と呼ばれることもあります。
関数との違い
通常の関数とクロージャは似ていますが、クロージャには名前がありません。そのため、必要な場所で直接定義して使うことができます。
// 通常の関数
func addNumbers(a: Int, b: Int) -> Int {
return a + b
}
// クロージャ
let addClosure = { (a: Int, b: Int) -> Int in
return a + b
}
// どちらも同じように呼び出せる
addNumbers(a: 3, b: 5) // 8
addClosure(3, 5) // 8
クロージャの基本構文
クロージャの基本的な書き方は以下の通りです。
{ (引数名: 型) -> 戻り値の型 in
処理
return 戻り値
}
具体例
let greet = { (name: String) -> String in
return "こんにちは、\(name)さん!"
}
print(greet("太郎")) // "こんにちは、太郎さん!"
クロージャの3つの形式
Swiftでは、クロージャは3つの形式で存在します。
1. グローバル関数
名前があり、値をキャプチャしない普通の関数です。
func multiply(a: Int, b: Int) -> Int {
return a * b
}
2. ネストされた関数
関数の中で定義された関数で、外側の関数の値にアクセスできます。
func makeIncrementer(increment: Int) -> () -> Int {
var total = 0
func incrementer() -> Int {
total += increment
return total
}
return incrementer
}
let addTwo = makeIncrementer(increment: 2)
print(addTwo()) // 2
print(addTwo()) // 4
3. クロージャ式
簡潔な構文で書ける無名のクロージャです。
let square = { (number: Int) -> Int in
return number * number
}
クロージャの実践的な使い方
配列操作での活用
クロージャは配列の操作で頻繁に使用されます。
map – 各要素を変換
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { (number: Int) -> Int in
return number * 2
}
print(doubled) // [2, 4, 6, 8, 10]
filter – 条件に合う要素を抽出
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let evenNumbers = numbers.filter { (number: Int) -> Bool in
return number % 2 == 0
}
print(evenNumbers) // [2, 4, 6, 8, 10]
reduce – 要素を集約
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { (result: Int, number: Int) -> Int in
return result + number
}
print(sum) // 15
sorted – ソート
let names = ["太郎", "花子", "次郎", "美咲"]
let sortedNames = names.sorted { (name1: String, name2: String) -> Bool in
return name1 < name2
}
print(sortedNames) // ["次郎", "太郎", "美咲", "花子"]
クロージャの省略記法
Swiftのクロージャは、様々な方法で簡潔に書くことができます。
1. 型推論による省略
コンパイラが型を推測できる場合、型を省略できます。
// 元のコード
let doubled = numbers.map { (number: Int) -> Int in
return number * 2
}
// 型を省略
let doubled = numbers.map { number in
return number * 2
}
2. 単一式でのreturn省略
クロージャの中身が1行だけの場合、return
を省略できます。
// returnを省略
let doubled = numbers.map { number in
number * 2
}
3. 引数名の省略(短縮引数名)
引数名の代わりに $0
, $1
, $2
といった短縮名を使えます。
// 引数名を省略
let doubled = numbers.map { $0 * 2 }
// 複数の引数がある場合
let sum = numbers.reduce(0) { $0 + $1 }
4. 演算子メソッド
演算子自体を渡すこともできます。
let sum = numbers.reduce(0, +)
print(sum) // 15
比較:すべての書き方
let numbers = [1, 2, 3, 4, 5]
// 完全な形
let result1 = numbers.map { (number: Int) -> Int in
return number * 2
}
// 型推論を使用
let result2 = numbers.map { number in
return number * 2
}
// returnを省略
let result3 = numbers.map { number in number * 2 }
// 短縮引数名を使用
let result4 = numbers.map { $0 * 2 }
// すべて同じ結果: [2, 4, 6, 8, 10]
トレイリングクロージャ
関数の最後の引数がクロージャの場合、括弧の外に書くことができます。これをトレイリングクロージャと呼びます。
// 通常の書き方
let result = numbers.map({ $0 * 2 })
// トレイリングクロージャ
let result = numbers.map { $0 * 2 }
引数がクロージャだけの場合は、括弧も省略できます。
値のキャプチャ
クロージャは、定義された場所の周囲の変数を「キャプチャ」して保持できます。
func makeCounter() -> () -> Int {
var count = 0
let counter = {
count += 1
return count
}
return counter
}
let myCounter = makeCounter()
print(myCounter()) // 1
print(myCounter()) // 2
print(myCounter()) // 3
この例では、クロージャが count
変数をキャプチャしており、関数の実行が終わった後も値を保持し続けます。
非同期処理でのクロージャ
クロージャは非同期処理でよく使われます。
// ネットワークリクエスト
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
// データの処理
print("データを取得しました")
}
}.resume()
// アニメーション
UIView.animate(withDuration: 0.5) {
view.alpha = 0
} completion: { finished in
print("アニメーション完了")
}
エスケープクロージャ
関数の実行が終わった後にクロージャが呼ばれる場合、@escaping
キーワードを付ける必要があります。
var completionHandlers: [() -> Void] = []
func saveCompletionHandler(handler: @escaping () -> Void) {
completionHandlers.append(handler)
}
saveCompletionHandler {
print("後で実行されます")
}
非エスケープクロージャ(デフォルト)は、関数内でのみ使用され、関数の実行が終わると破棄されます。
よくある使用例
1. ボタンのアクション
button.addAction(UIAction { action in
print("ボタンがタップされました")
}, for: .touchUpInside)
2. カスタムソート
struct Person {
let name: String
let age: Int
}
let people = [
Person(name: "太郎", age: 25),
Person(name: "花子", age: 30),
Person(name: "次郎", age: 20)
]
let sortedByAge = people.sorted { $0.age < $1.age }
3. 条件によるフィルタリング
let products = [
("りんご", 120),
("みかん", 80),
("バナナ", 150),
("ぶどう", 200)
]
let affordable = products.filter { $0.1 <= 150 }
// [("りんご", 120), ("みかん", 80), ("バナナ", 150)]
クロージャを使う際の注意点
1. 循環参照に注意
クロージャ内で self
を使用する場合、循環参照が発生する可能性があります。
// 悪い例
class ViewController {
var closure: (() -> Void)?
func setupClosure() {
closure = {
self.doSomething() // 循環参照
}
}
}
// 良い例
class ViewController {
var closure: (() -> Void)?
func setupClosure() {
closure = { [weak self] in
self?.doSomething() // weakで参照
}
}
}
2. 可読性とのバランス
短縮記法は便利ですが、過度に使用するとコードが読みにくくなります。
// 読みにくい
let result = data.filter { $0.1 > 100 }.map { $0.0 }.sorted { $1 < $0 }
// 読みやすい
let expensiveProducts = data.filter { price in price.1 > 100 }
let productNames = expensiveProducts.map { product in product.0 }
let sortedNames = productNames.sorted { name1, name2 in name2 < name1 }
まとめ
この記事では、Swiftのクロージャについて基本から実践まで解説しました。
重要なポイント
- クロージャは処理をまとめたコードブロック
- 配列操作で頻繁に使用される(map、filter、reduceなど)
- 様々な省略記法で簡潔に書ける
- 非同期処理でよく使われる
- 循環参照に注意が必要
クロージャを使いこなせるようになると、Swiftのコードがより簡潔で読みやすくなります。最初は完全な形で書いて、慣れてきたら少しずつ省略記法を使っていくのがおすすめです。
実際のアプリ開発では、配列の操作や非同期処理など、様々な場面でクロージャを使うことになります。この記事を参考に、ぜひクロージャを活用してみてください。