Swift クロージャのキャプチャについて
Swiftにおいて、クロージャは関数や変数と同様にオブジェクトとして扱われます。そして、クロージャが変数や定数、またはその他のオブジェクトを参照する場合、それらの値をキャプチャします。キャプチャは、クロージャが参照するオブジェクトを保持することによって行われます。この記事では、Swiftにおけるクロージャのキャプチャについて説明します。
クロージャのキャプチャ
クロージャが定義される時点で、クロージャは周囲の環境(スコープ)にアクセスできます。この周囲の環境にある変数や定数、またはその他のオブジェクトを、クロージャが参照することができます。この参照は、クロージャがオブジェクトを保持することによって行われます。この保持のことを「キャプチャ」と呼びます。
例えば、以下のコードを考えます。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() // 10
incrementByTen() // 20
このコードでは、makeIncrementer 関数は、クロージャを返します。返されるクロージャは、引数 amount に渡された値を、その都度 runningTotal に加算していくものです。ここで、incrementByTen は、makeIncrementer(forIncrement: 10) で返されたクロージャを保持する変数です。そして、incrementByTen を実行するたびに、その保持しているクロージャが実行され、runningTotal が更新されます。
このように、クロージャがオブジェクトをキャプチャすることで、クロージャは周囲の環境にアクセスし、そのオブジェクトを保持します。
キャプチャの種類
Swiftには、3種類のキャプチャがあります。それぞれのキャプチャは、クロージャ内で変数をどのように扱うかを決定します。
強い参照キャプチャ(Strong reference capture)
強い参照キャプチャは、クロージャがキャプチャするオブジェクトに強い参照を持つことを意味します。これは、クロージャがオブジェクトを保持している間、オブジェクトがメモリから解放されないことを意味します。
以下は、強い参照キャプチャを使った例です。
class MyClass {
var value = 0
lazy var closure: () -> Int = {
self.value += 1
return self.value
}
deinit {
print("MyClass deinit")
}
}
var myObject: MyClass? = MyClass()
myObject?.closure() // 1
myObject?.closure() // 2
myObject = nil // MyClass deinit
この例では、クロージャが MyClass のインスタンスをキャプチャしています。closure プロパティを呼び出すたびに、 value の値がインクリメントされます。このクロージャは、オブジェクトを保持しているため、 myObject を nil に設定することでオブジェクトを解放することができます。
弱い参照キャプチャ(Weak reference capture)
弱い参照キャプチャは、クロージャがキャプチャするオブジェクトに弱い参照を持つことを意味します。弱い参照を持つと、オブジェクトがメモリから解放された後も、クロージャが参照を持ち続けることはありません。
以下は、弱い参照キャプチャを使った例です。
class MyClass {
var value = 0
lazy var closure: () -> Int = { [weak self] in
self?.value += 1
return self?.value ?? 0
}
deinit {
print("MyClass deinit")
}
}
var myObject: MyClass? = MyClass()
myObject?.closure() // 1
myObject?.closure() // 2
myObject = nil // MyClass deinit
この例では、クロージャが MyClass のインスタンスを弱い参照でキャプチャしています。closure プロパティを呼び出すたびに、 value の値がインクリメントされます。ただし、self が nil である場合には、デフォルト値として 0 を返します。myObject を nil に設定しても、MyClass のインスタンスは解放されます。弱い参照でキャプチャしているので明示的にnilを設定しなくても、オブジェクトが解放されると自動的にnilになります。
未所有キャプチャ(Unowned reference capture)
未所有キャプチャは、弱い参照キャプチャと同様に、クロージャがキャプチャするオブジェクトに弱い参照を持ちます。ただし、未所有キャプチャは、オブジェクトがメモリから解放された後でも、クロージャが参照を持ち続けることができます。このため、オブジェクトが解放された後にクロージャを呼び出すと、ランダムなメモリエラーが発生する可能性があります。
未所有キャプチャは、オブジェクトが存在することが保証されている場合に使用されます。また、オブジェクトが解放された後にクロージャが呼び出される可能性がある場合には、弱い参照キャプチャを使用する必要があります。
以下は、未所有キャプチャを使用した例です。
class MyClass {
var value = 0
lazy var closure: () -> Int = { [unowned self] in
self.value += 1
return self.value
}
deinit {
print("MyClass deinit")
}
}
var myObject: MyClass? = MyClass()
myObject?.closure() // 1
myObject?.closure() // 2
myObject = nil // MyClass deinit
この例では、未所有キャプチャを使用しています。クロージャが MyClass のインスタンスをキャプチャしていますが、[unowned self] という構文を使用することで、クロージャがオブジェクトを所有しないことを明示しています。このクロージャは、オブジェクトを解放してもクロージャを呼び出すことができます。
以上が、Swiftにおけるキャプチャの種類についての説明です。適切なキャプチャを選択することで、メモリリークやクラッシュなどの問題を回避することができます。