😺

中学生でもわかる、Swiftのキャプチャリスト

2024/10/25に公開

1. キャプチャリストって何?

キャプチャリストとは、クロージャ(後で実行されるコードのまとまり)内で使われる変数の「コピーの仕方」を指定するための機能です。

例えば、クロージャの外で宣言された変数をクロージャの中で使いたいとき、Swiftはその変数を「キャプチャ」します。このキャプチャの方法を調整するためにキャプチャリストが使われます。

2. なぜキャプチャするの?

Swiftのクロージャは、変数をクロージャの外から持ってくることができます。例えば、以下のようなコードがあります:

var name = "Taka"

let greeting = {
    print("Hello, \(name)!")
}

この場合、greeting クロージャは name をキャプチャして、後で greeting() が呼ばれると name の中身を使って「Hello, Taka!」と表示します。

でも name の値が変わったらどうなるでしょうか?

name = "Yuta"
greeting()  // -> "Hello, Yuta!"

ここで気づくのは、name は変更されても新しい値を使っているということです。このように、クロージャは変数をそのまま参照しているんです。

3. キャプチャリストの使い方

しかし、場合によっては、変数の「コピー」を保持したいときや、メモリの扱いを制御したいこともあります。ここで登場するのがキャプチャリストです。

キャプチャリストは、クロージャ内で使う変数の参照方法を変更するために使います。実際には、変数の前に [] で囲んで指定します。

var name = "Taka"

let greeting = { [name] in
    print("Hello, \(name)!")
}
name = "Yuta"
greeting()  // -> "Hello, Taka!"

この場合、キャプチャリスト [name] を使うことで、name のコピーがクロージャの中に保存され、後から name が変更されてもクロージャは「コピーされた古い値」を使います。

4. 実際の利用パターン

(1) メモリ管理

クロージャが変数を強く(しっかりと)キャプチャしてしまうと、メモリが解放されずにアプリがメモリを無駄に使ってしまうことがあります。これを避けるために、キャプチャリストで weak(弱い参照)を使います。

class Person {
    var name = "Taka"
    
    func greet() {
        let greeting = { [weak self] in
            print("Hello, \(self?.name ?? "someone")!")
        }
        greeting()
    }
}

ここでは selfweak としてキャプチャすることで、Person オブジェクトがメモリから解放されることを防ぎます。もし self をそのまま強くキャプチャしてしまうと、Person がメモリから消えなくなってしまう可能性があるんです。

(2) 値を一度だけキャプチャしたいとき

値を変更されないように、クロージャの中で「その時の状態」を使いたい場合にもキャプチャリストを使います。例えば、タイマーや遅延した処理を実行するときです。

var counter = 10

let countdown = { [counter] in
    print("Countdown: \(counter)")
}

counter = 5
countdown()  // -> "Countdown: 10"

[counter] とすることで、countdown クロージャが呼ばれたときには「最初にクロージャが作られた時の値(10)」を使います。これで、counter が後から変更されても、クロージャ内の値は変わりません。

(3) クロージャとUIの連携

例えば、SwiftUIでボタンをタップした時に何かを変える場合、クロージャ内でUIの状態を変更することがあります。キャプチャリストで状態管理をすると、その時のUIの状態をしっかり覚えてくれます。

struct ContentView: View {
    @State private var count = 0

    var body: some View {
        Button("Tap me") {
            [count] in
            print("Button tapped when count was \(count)")
        }
    }
}

この例では、Button がタップされた時に、ボタンが押された時の count の値を記録しています。

まとめ

キャプチャリストは、クロージャの中で変数をどのようにキャプチャするかをコントロールするために使います。特に、メモリ管理や状態の保持などで役立つ場面が多いです。

簡単にまとめると:

  • キャプチャリストを使うと、変数の参照方法を制御できる。
  • メモリリークを防ぐために weakunowned を使うことがある。
  • 変数の変更をクロージャの中で反映させないように、値をコピーしてキャプチャすることもできる。

これでキャプチャリストがどういうものか、どう使われるかのイメージがついたかな?

補足のコード

NonCaputured
let names = ["Taka", "Nana", "Lynn"]
var name = "Taka"
var greetings: [() -> ()] = []

for newName in names {
    name = newName
    greetings.append({
        print("Hello, \(name)")
    })
}
for greet in greetings {
    greet()
}

Hello, Lynn
Hello, Lynn
Hello, Lynn
最終的にname = "Lynn"のため

Caputured
for newName in names {
    name = newName
    greetings.append({ [name] in // Capture
        print("Hello, \(name)")
    })
}

Hello, Taka
Hello, Nana
Hello, Lynn
その都度、値をキャプチャーする。

Discussion