Swiftコードでは、ソート条件、ネットワークの完了ハンドラー、ボタンアクション、SwiftUIのbodyまで、クロージャがいたるところに登場します。しかし「クロージャが値をキャプチャするとは何か」「[weak self]はなぜ使うのか」「@escapingはなぜ付けるのか」と聞かれると、説明に詰まる人も少なくありません。
この3つの疑問は、実は一つにつながっています。クロージャが周囲の値をキャプチャする仕組みを理解すれば、参照型である理由、循環参照が起きる理由、escapingの指定が必要な理由まで連続して理解できます。Swift基礎シリーズ第2回では、そのつながりを順に見ていきます。
クロージャとは — 名前より重要な「捕まえる」性質
まず構文を最小限に整理します。クロージャは実行コードのブロックを値として扱う構文です。変数に格納し、引数として渡し、返すことができます。
let add: (Int, Int) -> Int = { a, b in a + b }
add(2, 3) // 5
実は、funcで宣言する関数も名前付きクロージャです。Swiftでは関数とクロージャは同じ仲間で、{ }構文は名前を付けずその場で作る形にすぎません。
closureという名前は「コードブロック」ではなく、周囲の変数を包み込んで閉じる(close over)性質に由来します。
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
counter() // 1
counter() // 2
counter() // 3
不思議なことが起きています。countはmakeCounterのローカル変数なので、関数が返れば消えるはずです。それなのに返されたクロージャを呼ぶたび増え続けます。クロージャが自分の外側にある変数countをキャプチャし、関数終了後も生かしているからです。これがクロージャの本質で、この記事の残りはすべてこのキャプチャから派生します。
キャプチャの正確な意味 — コピーではなく参照
Swiftクロージャの基本のキャプチャ方式は参照です。値をコピーするのではなく、その変数自体とのつながりを保ちます。そのため次の結果になります。
var multiplier = 2
let times = { (n: Int) in n * multiplier }
times(10) // 20
multiplier = 3
times(10) // 30 — クロージャは変更後の値を見る
使われたのは、クロージャ作成時のmultiplier(2)ではなく、実行時のmultiplier(3)です。クロージャは変数のスナップショットではなく、変数そのものを持ち運びます。
作成時点の値を固定したい場合は、キャプチャリストを使います。
let times = { [multiplier] (n: Int) in n * multiplier }
times(10) // 20
multiplier = 3
times(10) // 20 — 作成時点の 2で固定
角括弧内に書いた変数はクロージャ生成時にコピーされ、クロージャ内の定数として固定されます。まとめると、基本は参照キャプチャ(生きたつながり)、キャプチャリストは値キャプチャ(生成時点のスナップショット)です。[weak self]を説明する前に、この区別を押さえる必要があります。
このキャプチャ用ストレージがあるため、クロージャは参照型です。キャプチャされた変数はクロージャと同じ寿命を持つ必要があるためヒープに保存され、クロージャ値をコピーすると同じストレージを共有する参照が一つ増えます。struct中心のSwiftでクロージャがクラスのように振る舞う理由です。値型と参照型の区別に慣れていなければ、まず値型優先主義の記事を読んでください。
循環参照と[weak self] — キャプチャが生む典型的な事故
参照キャプチャの代償が循環参照です。構図はいつも同じです。
class ProfileViewModel {
var onUpdate: (() -> Void)?
var name = ""
func bind() {
onUpdate = {
print("名前: \(self.name)")
}
}
}
ビューモデルはonUpdateプロパティを通じてクロージャを強く所有します。一方、そのクロージャはself、つまりビューモデルを参照キャプチャしています。ビューモデル → クロージャ → ビューモデルという所有の輪が閉じ、ARCの世界では互いを保持したまま永遠に解放されません。画面を閉じてもビューモデルがメモリに残り、リークが完成します。
解決策は、キャプチャリストの2つ目の用途です。[weak self]はselfを弱参照でキャプチャする指示です。クロージャはselfを保持しますが所有しないため、輪が切れます。その代わりselfが先に解放される可能性があるので、クロージャ内のselfはオプショナルになり、通常はguard let self else { return }から始めます。オプショナルの記事で扱った早期脱出パターンがここでも登場します。
onUpdate = { [weak self] in
guard let self else { return }
print("名前: \(self.name)")
}
重要なのは、[weak self]が万能な接頭辞ではないことです。循環には「selfがクロージャを所有し、そのクロージャがselfをキャプチャする」という条件が必要です。この条件がなければweakも不要です。たとえばDispatchQueue.main.asyncAfterに渡すクロージャは実行後にシステムが破棄するため、selfが少し長く生きるだけでリークではありません。反射的にweakを付けるのではなく、「このクロージャを誰がどれだけ長く所有するか」と考えるのが正確な習慣です。NSTimerのように所有構造が特殊で有名な例は別の記事で扱います。
@escaping — クロージャが関数より長く生きるとき
最後のピースです。関数パラメーターとして受け取るクロージャには、ときどき@escapingが付きます。
func fetchUser(completion: @escaping (User) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, _ in
let user = parse(data)
completion(user) // 関数が返ったずっと後に実行される
}.resume()
}
基準は実行時点です。関数が返る前に内部で実行して破棄するクロージャはnon-escaping(デフォルト)です。プロパティに保存したり非同期処理に渡したりして、関数の返却後に実行される可能性があればescaping、つまり関数の外へ逃げるクロージャです。
なぜ区別を強制するのでしょうか。クロージャがescapingかどうかで、コンパイラーと開発者の考え方が変わるからです。non-escapingは関数実行中だけ生きることが保証されるため、コンパイラーはキャプチャストレージを最適化でき、循環参照の心配も根本的にありません。mapやfilterに渡すクロージャでselfを気にしなくてよいのはそのためです。一方escapingはクロージャがどこかに保存され長く生きることを意味するため、前述の循環参照の確認対象になります。@escapingは「このクロージャは長く生きるのでキャプチャに注意」というAPIレベルの警告ラベルです。
completionハンドラー形式のescapingクロージャは、async/awaitの導入により新しいコードでは減少傾向にあります。それでも既存APIを読んだりブリッジしたりするには、正確に理解しておく必要があります。
実践で使う3つの基準
理論を実務向けにまとめると、基準は3つです。
**1つ目、クロージャを見たらまず寿命を考えます。**関数内で消費されて終わるのか(non-escaping)、どこかに保存されて長く生きるのか(escaping)。この一問で、キャプチャをどれだけ気にすべきかが決まります。
**2つ目、weakは反射ではなく判断で使います。**プロパティに保存するハンドラーやデリゲート的コールバックなど、所有の輪が実際に閉じる場所には[weak self]を使います。輪のない一度きりの実行には不要です。判断に迷ったら、InstrumentsのLeaksやdeinitログで確認する習慣を付けましょう。
**3つ目、キャプチャリストで意図を文書化します。**値を固定したいなら[value]、所有したくないなら[weak self]です。キャプチャリストは性能の仕組みである以前に、「このクロージャが外の世界とどうつながるか」をコードに明示する文書です。
まとめ
- クロージャの本質はコードブロックではなくキャプチャ、つまり周囲の変数を包み込んで閉じ、寿命を延ばす性質です。
- 基本のキャプチャはコピーではなく参照なので、クロージャは実行時の値を見ます。キャプチャリスト[x]は生成時点の値に固定します。
- キャプチャストレージを共有する必要があるため、クロージャは参照型です。
- selfが所有するクロージャがselfをキャプチャすると循環参照になります。[weak self] + guard let selfが標準解です。ただし所有の輪がなければweakも不要です。
- @escapingは「関数より長く生きるクロージャ」を示す警告ラベルで、キャプチャを確認すべき箇所を知らせます。
次回は、保存プロパティと計算プロパティ、lazy、プロパティオブザーバーまで、変数宣言1行に隠れた選択肢を整理します。

![[Swift基礎 #2] クロージャのキャプチャとweak self、escapingのカバー画像](/assets/images/posts/acbedb03-d119-4e28-b248-ff183d5af89c/1.jpg)