この記事はレコチョク Advent Calendar 2025 の15日目の記事となります。
🎄 はじめに
株式会社レコチョクの上野です。
2025年4月に新卒で入社し、約半年の研修期間を経て、PlayPASSのiOSアプリ開発に携わっています。
邦ロック好きで、最近はChilli Beans.、レトロリロン、多次元制御機構よだかの曲をよく聴いています。最近の激アツトピックは、自分がハマったときには解散していたSUNNY CAR WASHの再結成が決まったことです。
💭 「処理の流れ」がわからない
プロダクトにアサインされ、Swiftで実装を始めて約3カ月。ある画面のレイアウト開発を担当することになりました。
先輩方が書いた過去のコードを見ながら、レイアウトを設計するコードを考え始めました。参考にしたコードは以下のような形です。
collectionView.snp.makeConstraints {
$0.edges.equalTo(view.safeAreaLayoutGuide)
}
collectionViewに対してSnapKitを用いてAuto Layoutの制約を設定するコードです。
コードを参考にとりあえず書いてみたものの、この書き方にどこかしっくりこない感がありました。単語からパーツを読み取って「collectionViewに対して制約を設定している」「外枠に合わせた制約を設定している」というざっくりとした実装は理解できました。
しかし、どこかコードが部分的に省略されているように感じ、処理の流れが理解できませんでした。
🤔{ ... }に囲われていて関数っぽいけれどreturnはどこにあるの?
🤔 もし関数だとしたら関数名はどこに書かれているの?
🤔$0って数字、どこから湧いてきた?
制約の設定について記載されている他のコードを見ても、どれも同じ書き方がなされていました。したがって、このコードは「言語習得者から見ると当たり前に理解できるけれど、初学者には理解しづらいコード」だということがわかりました。
調べていくと、{ ... }で表されている部分はクロージャという概念が用いられており、しばしば省略形が使われていることがわかりました。
そこで、上記の疑問を解消するために、クロージャとその省略形について整理してみることにしました。
🤔 クロージャとは
クロージャとは、そもそもどのようなもののことを指すのでしょうか。
公式ドキュメントでは次のように書かれています。
Closures are self-contained blocks of functionality that can be passed around and used in your code. クロージャは、コード内で受け渡しや利用ができる、自己完結した機能のブロックです。
─ Closures · The Swift Programming Languageより
つまり、任意の処理のまとまりをグループ化したものをいいます。他のプログラミング言語におけるラムダ関数やブロックが似た機能を持っています。
クロージャは、定義されたスコープの外に出ても、クロージャ内部で使われた変数や定数を保持し続けることができます。これをキャプチャといいます。
クロージャはグローバル関数・ネスト関数・クロージャ式の3つの形式にわけることができます。それぞれ「名前の有無」と「キャプチャ可能か」によって判別することができます。
| 形式 | 名前 | キャプチャ |
|---|---|---|
| グローバル関数 | あり | ❌ 不可 |
| ネスト関数 | あり | ✅ 可能 |
| クロージャ式 | なし | ✅ 可能 |
🌏 グローバル関数(名前あり / キャプチャ不可)
var globalRunningTotal = 0
let globalAmount = 10
func incrementGlobal() -> Int {
globalRunningTotal += globalAmount
return globalRunningTotal
}
print(incrementGlobal()) // 10
print(incrementGlobal()) // 20
print(incrementGlobal()) // 30
グローバル関数は、いわゆる名前付きの通常の関数のことです。
グローバル関数は、グローバル変数に直接アクセスできます。したがって、キャプチャという概念がなく、キャプチャすることはできません。
上記の場合、状態はグローバル変数globalRunningTotalで保持されます。
🏠 ネスト関数(名前あり / キャプチャ可能)
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen()) // 10
print(incrementByTen()) // 20
print(incrementByTen()) // 30
let incrementBySeven = makeIncrementer(forIncrement: 7)
print(incrementBySeven()) // 7
print(incrementByTen()) // 40
ネスト関数は、関数(親関数)の中で作成された関数のことです。親関数の処理の中でしか呼び出すことができず、関数の外側から利用することはできません。
上記の場合、ネスト関数incrementerが外側の変数runningTotalと引数amountをキャプチャします。
通常、ローカル変数は関数の実行が終わると参照できなくなります。
しかし、ネスト関数が内部の値をキャプチャしているので、makeIncrementerの実行処理後も値が保持され、同じ処理を行うことができます。
incrementByTenとincrementBySevenではそれぞれ保持されている値が異なるため、独立した結果が返ってきています。
📦 クロージャ式(名前なし / キャプチャ可能)
func makeIncrementerClosure(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
return {
runningTotal += amount
return runningTotal
}
}
let incrementByTenClosure = makeIncrementerClosure(forIncrement: 10)
print(incrementByTenClosure()) // 10
print(incrementByTenClosure()) // 20
print(incrementByTenClosure()) // 30
let incrementBySevenClosure = makeIncrementerClosure(forIncrement: 7)
print(incrementBySevenClosure()) // 7
print(incrementByTenClosure()) // 40
クロージャ式は、関数をより簡潔に書くための文法です。funcを使った関数定義と異なり、{ ... }の形だけで処理を書くことができ、関数名を持ちません。その場で値として扱えるため、関数の引数として渡したり、戻り値として返したりする際に特に有用です。
例えば、return後に{ ... }を書くことで、クロージャ式として機能し、関数と同様に使用できます。
クロージャ式の特徴は、外側のスコープにある変数をキャプチャ(保持)できることです。つまり、クロージャ式は単なる処理の塊ではなく、必要に応じて外部の値を取り込み、それを保持したまま動作できます。
関数とクロージャ式の違い
関数とクロージャ式はどちらも{ ... }を用いますが、それぞれどのような違いがあるのでしょうか。
関数(グローバル関数・ネスト関数)funcとクロージャ式{ ... }で明確に異なるのは、生成されるタイミングです。
関数は、コンパイル時に固定された「処理(コード)」として生成されます。つまり、関数は処理だけを持つ構造で、毎回同じ処理を実行します。
一方でクロージャ式は、実行時に生成されます。プログラムが{ ... }に到達したときにクロージャのインスタンスが作られ、必要であれば外側の値をキャプチャし、「処理(コード)」と「値(環境)」をセットとして保持します。
例外として、ネスト関数は本来「ただの関数」として扱われますが、外側のスコープにあるローカル変数にアクセスする場合に、必要な変数がキャプチャされ、クロージャとして扱われます。
- クロージャ式:キャプチャした値を生かして後で実行したい(キャプチャがメイン)
- ネスト関数:関数内部に処理をカプセル化して、ロジックを整理したいとき(必要ならキャプチャ)
と覚えておきましょう。
✂ 省略できるコード
クロージャの中でも、クロージャ式では関数名の省略だけでなく、より簡潔にコードを書くことができます。
クロージャ式の基本形は以下のとおりです。
{(引数) -> 戻り値の型 in
実行するコード
}
例: サンプルコード
// 引数に1を足して返すクロージャ
let addOne = { (number: Int) -> Int in
return number + 1
}
上記のサンプルコードを用いて、クロージャがどのように省略されるか見ていきましょう。
1. returnを省略
let addOne = { (number: Int) -> Int in
number + 1
}
returnの処理が1行(単一式)の場合、省略することができます。
2. 戻り値の型を省略
let addOne = { (number: Int) in
number + 1
}
型推論ができる場合、型の表記を省略することができます。上記の場合
-> Intを省略できます。
3. 引数の型を省略、()を省略
let addOne = { number in
number + 1
}
2と同様に型推論により、引数の型の表記も省略することができます。: Intと括りを省略できます。
4. 引数自体を省略
let addOne = {
$0 + 1
}
引数の型のみならず、引数自体を省略することができます。引数の1つ目を使用したい場合、$0を使用します。
また、引数が2つ以上ある場合、$1, $2と続けて使用することができます。
冒頭で記載したコードにも、実際にはさまざまな処理が省略されていることがわかりました。
/// 省略前(完全形):
collectionView.snp.makeConstraints { (make: ConstraintMaker) -> Void in
make.edges.equalTo(view.safeAreaLayoutGuide)
}
/// 省略後:
collectionView.snp.makeConstraints {
$0.edges.equalTo(view.safeAreaLayoutGuide)
}
/// → 戻り値の型、引数の型、引数名がすべて省略されている。
🛠 クロージャはこう活用する
最後に、具体的に業務で触れたコードを用いて、クロージャの有用性について解説したいと思います。
以下のコードは、NSCollectionLayoutItemで、セクションに応じてあるitemの高さの計算方法を切り替えている例です。
let height: NSCollectionLayoutDimension
if sectionIndex == 0 {
height = .estimated(100)
} else {
height = .fractionalHeight(1.0)
}
let item = NSCollectionLayoutItem(layoutSize: .init(
widthDimension: .fractionalWidth(1.0),
heightDimension: height
))
引数であるheightDimensionの高さを調整したい場合、heightのような一時変数を定義する必要があります。
ただ1つの引数の値を条件によって変化させたいのですが、
- UIの宣言と条件文の処理が離れる
- なぜ高さを切り替えるかの意図が読み取りにくい
- 単純な条件文に対してコードの量が増える
といったデメリットを生みます。
クロージャを用いて書き換えると以下のようなコードになります。
let item = NSCollectionLayoutItem(layoutSize: .init(
widthDimension: .fractionalWidth(1.0),
heightDimension: {
guard sectionIndex == 0 else {
return .fractionalHeight(1.0)
}
return .estimated(100)
}()
))
1つの引数に対して、
- 固定した高さを使うか
- 内容に応じて変化する高さにするか
という2つのレイアウト仕様を同時に持たせています。
この際、クロージャ{ ... }に()をつける即時実行クロージャを使っています。通常のクロージャは定義しただけでは動きませんが、即時実行クロージャは「宣言しながらその場で実行して値を返す」ことができます。
📝 まとめ
今回はSwiftにおけるクロージャについて解説しました。
🤔{ ... }に囲われていて関数っぽいけれどreturnはどこにあるの?
✅{ ... }はクロージャ。returnは処理が1行(単一式)の場合省略できる。
🤔 もし関数だとしたら関数名はどこに書かれているの?
✅ クロージャは大きく3つにわけられる。中でもクロージャ式は名前が必要ない。
🤔$0って数字、どこから湧いてきた?
✅$0は引数自体の省略形。引数が増える場合は$1→$2→…と数字を続けて使用できる。
実務では、型やreturnを省略して書かれることが多いですが、そればかり意識すると、かえって可読性が下がることもあります。その際にはコメントで詳細を書いてあげるなどで、読みやすいコードにしてあげることも大切です。
現代ではAIを用いることで、省略されたコードを展開してくれるので、うまく活用することで理解が深まると思います。
クロージャは強力な機能ですが、可読性と簡潔さのバランスを意識して使うことが大切だと感じました。
明日の レコチョク Advent Calendar 2025 は16日目「AWS MCP Server (Preview) を Cursor 経由で試してみた」です。お楽しみに!
参考文献
上野 翔碁