ギョムでコードを書いていて、質問が来て、どうやら認知度は高くないというテクだったらしいので折角なのでシェアしてみる。
Swiftでのクロージャの構文はこう。
class Foo {
func bar() {
let x: (Int) -> Int = { a in a + 1 }
}
}
引数をinの前に列挙する。宣言時に参照可能な変数をキャプチャすることも出来る。この時対象のretain countが増える。
class Foo {
func bar() {
let b = 2
let x: (Int) -> Int = { a in a + b }
}
}
retain countが増えるので、self
を参照し、クロージャをキャプチャしてしまうと循環参照が生まれることがある。
class Foo {
var inc: Int = 2
var myClosure: ((Int) -> Int)?
func bar() {
myClosure = { a in a + self.inc } // self <-> myClosure の循環参照ができる。
}
}
これを避けるのに一般に使われる方法が、unowned
, weak
だ。前者はクラッシュの危険があり、後者はOptionalになる。
これらを用いて最も安全に書くなら、 weak
とguard
を組み合わせる方法だろうか。
class Foo {
var inc: Int = 2
var myClosure: ((Int) -> Int)?
func bar() {
myClosure = { [weak self] a in
guard let `self` = self else { fatalError() } // 発生しないと保証する。こうするならunownedでも良い。
return a + self.inc
}
}
}
実はlet
プロパティなら、変更されないことが保証されているため、weak
もunowned
も使わない方法がある。
class Foo {
let inc: Int = 2
var myClosure: ((Int) -> Int)?
func bar() {
myClosure = { [inc] a in a + inc }
}
}
キャプチャされるのはincなので、self
のリテインカウントは増えない。
inc
は値型なのでこの場合はクロージャの宣言時にコピーされる。
注意が必要なのが、プロパティをvar
で宣言している場合、クロージャの宣言時に存在したプロパティを対象にしているので変更しても反映されなくなってしまうという点。これはプロパティの種類が値型かクラスであるかは関係なく、どちらでも発生する。let
に限定して使うのが良いだろう。
クロージャの中に「init以降変更することのないプロパティ」を渡したい、という場面は多くあるはず。
毎回guard
して長いコードを書いたり、unowned
を使ってクラッシュに怯えるならば、この方法使ってみては如何でしょうか。