はじめに
Swiftのクロージャがキャプチャする仕組みと、weak
を使うべきケースと使わなくてよいケースについて書きます。
クロージャのキャプチャとは?
クロージャは定義されたときに、スコープ内の変数やオブジェクトを 保持(キャプチャ) します。
値型(struct
や let
)のキャプチャ
struct Person {
let name: String
}
let person = Person(name: "Alice")
let closure = {
print(person.name) // キャプチャされる
}
closure() // "Alice"
✅ ポイント:
-
struct
やlet
の変数は、値のコピーとしてキャプチャされる -
closure
の中でperson
を変更することはできない
参照型(class
)のキャプチャ
class Person {
var name: String
init(name: String) { self.name = name }
}
var person = Person(name: "Alice")
let closure = {
print(person.name) // キャプチャされる
}
closure() // "Alice"
✅ ポイント:
-
class
のインスタンスは 参照としてキャプチャ される -
closure
の外でperson.name
を変更すると、closure
内のperson
にも影響する
強参照サイクル(メモリリーク)のリスク
クロージャが self
をキャプチャすると、強参照サイクルが発生し、メモリリークの原因になることがあります。
問題となるコード
class ViewController {
var title = "Hello"
func setupClosure() {
let closure = {
print(self.title) // self をキャプチャ
}
closure()
}
}
✅ 問題点:
-
closure
はself.title
を参照するため、self
をキャプチャする -
self
が解放されない可能性がある
解決策: [weak self]
を使う
class ViewController {
var title = "Hello"
func setupClosure() {
let closure = { [weak self] in
print(self?.title ?? "No title") // self を弱参照
}
closure()
}
}
✅ [weak self]
の効果:
-
self
を 弱参照 することで、メモリリークを防ぐ -
self
がnil
になる可能性があるので、self?
で安全にアクセス
3. weak
を使うべきケースと使わなくてよいケース
キャプチャが発生する場合 | キャプチャしない場合 |
---|---|
クロージャの中で self にアクセスしている |
self を使っていない |
クロージャの中でクラスのプロパティを参照している | クロージャ内で独立した変数だけを使っている |
クロージャが self を長期間保持する |
クロージャが一時的に実行される |
✅ weak
を使うべきケース
1. クラスのプロパティとしてクロージャを保持する場合
class ViewController {
var closure: (() -> Void)?
func setupClosure() {
closure = { [weak self] in
print(self?.title ?? "No title")
}
}
}
✅ 理由:
-
closure
がself
をキャプチャすると、self
が解放されない
2. 非同期処理の中で self
を参照する場合
class ViewController {
func fetchData() {
DispatchQueue.global().async { [weak self] in
self?.updateUI()
}
}
}
✅ 理由:
- ネットワーク通信などで
self
をキャプチャすると、完了するまでself
が解放されない可能性がある
❌ weak
を使わなくてよいケース
1. クロージャが即時実行される場合
func doSomething() {
let closure = {
print("Hello") // self をキャプチャしない
}
closure()
}
✅ 理由:
-
closure
はself
に依存しておらず、すぐに実行されるので問題なし
2. クロージャのスコープ内で完結する場合
func performAction() {
let localVar = "Swift"
let closure = {
print(localVar) // キャプチャするが問題ない
}
closure()
}
✅ 理由:
-
localVar
はStrin
なので、値コピーされるためメモリリークの心配なし
まとめ
✅ クロージャがキャプチャするのは、外部の変数や self
にアクセスする場合
✅ class
のインスタンスをキャプチャすると、強参照サイクルが発生する可能性がある
✅ [weak self]
を使うことで、メモリリークを防ぐ
✅ 即時実行や値コピーの場合は weak
は不要
クロージャのキャプチャを正しく理解し、適切に weak
を使うことで、メモリ管理のバグを防ぐことができます!