##じょ
某所で好評を得ているのでこちらに転載します。
これはなに?
以下のようなよくあるサンプルコードのguard let self = self else { return }
が何のためにあるのかよくわからない、という疑問にお答えします。
func didTapSignUpButton() {
Auth.auth().createUser(withEmail: email, password: password) { [weak self] result, error in
guard let self = self else { return }
if let user = result?.user {
// do something
}
}
}
短い回答
[weak self]
でキャプチャしたself
がクロージャ実行時に存在しているかどうかを確認しています。
長い回答
guard let else
まず、
guard let xxx = yyy else { return }
はyyy
がOptionalで、yyy
に値がなければreturnして値があればxxx
にその値を入れる、という構文です。
例えば以下のように動きます。
func hoge(_ value: Int?) {
gurad let v = value else { return }
print(v)
}
hoge(10) // prints 10
hoge(nil) // no prints
###変数のキャプチャ
次に、関数による変数のキャプチャというものがあります。
var val = 0
func fuga() {
val += 2 // valをキャプチャ
}
print(val) // prints 0.
fuga()
print(val) // prints 2.
関数にキャプチャされた変数valは関数内で値を変更することが可能です。
Swiftでは関数とクロージャは同等のものですので以下でも同じです。
var val = 0
let fuga = { val += 2 }
###インスタンスのオーナーシップ
最後にクラスのインスタンスのオーナーシップです。
クラスのインスタンスにはデータ領域がありますので、そのインスタンスを使わなくなった時にその領域が解放されなければメモリがひっ迫してしまいます。
しかし、使用中に開放されてしまうと、アプリケーションがクラッシュする事態になります。
このためSwiftではインスタンスにオーナーシップというものを設定しこれに対処しています。
原則として、使用中はオーナーを設定し使用が終わるとそれを外します。オーナーがすべてなくなった段階でインスタンスは解放されます。
これらは自動的に行われており、通常はプログラマは何も考える必要もありません。
ただし、循環参照という状態になるとインスタンスが解放されなくなるため、これを対処する必要があります。
class A {
let b: B
init(b: B) {
self.b = b
b.a = self
}
}
class B {
var a: A?
}
let a: A? = A(b: B())
この例は意味のあるコードではありませんが、循環参照の典型的な例です。
class Aのインスタンスはclass Bのインスタンスを、class Bのインスタンスはclass Aのインスタンスを参照しています。
そのため、上の例でa = nil
としてもa
, b
どちらのオーナーもなくならないため、どちらも解放されません。
この循環参照を回避するためにweak
という修飾子が用意されています。
上の例ではclass Bの変数a
をweak
とします。
class B {
weak var a: A?
}
このweak
という修飾子は「インスタンスは参照するがオーナーとしては登録するな。またインスタンスが解放されたとき値をnil
にせよ。」という指示を与えます。
似た修飾子にunowned
というものがあります。最後に少し説明をします。
こうすることでa = nil
が実行されると、a
のオーナーがすべてなくなりa
が解放され、それによってb
のオーナーもなくなるためb
も解放されるようになります。
###キャプチャによるオーナシップの獲得
まだまだ続きます。
つぎは今までできたことの複合技、キャプチャによるオーナシップの獲得です。
これは名前の通りです。先ほどのclass Aを使います。
var a: A? = A(b: B())
let f = { print(a) } // インスタンスaをキャプチャ
a = nil // インスタンスaは解放されない
クロージャがインスタンスaをキャプチャした時、自動的にオーナーが設定されるため、a = nil
を実行してもインスタンスaは解放されません。ただしこの場合は、f
が使われなくなった時にf
と一緒にa
も解放されます。
これにも循環参照の問題が発生します。
class C {
var f: (() -> Void)?
}
let c: C? = C()
c?.f = { print(c) }
c = nil // c は解放されない
このようにインスタンスc
が持つクロージャがcをキャプチャしてしまったため、循環参照が発生し、c
が解放されなくなってしまいます。
この時もweak
修飾子を使います。ただし、クロージャ自体をweak参照することができないため以下のように行います。
c?.f = { [weak c] in print(c) }
このweak
はc
をキャプチャする時にオーナーを設定するなという指示になります。
この指定をすることでc = nil
の時にインスタンスc
が解放されるようになります。
ところが、これでも問題が発生する場合があります。class Aを使います。
var a: A? = A(b: B())
let f = { [weak a] in print(a) }
a = nil
f() // prints nil
a
が解放された後にクロージャfを実行しようとすると、クロージャ内で使用しているa
がすでに存在していないためnil
になってしまいます。
###キャプチャしたインスタンスの存在確認
この問題を解決するためには実行前にインスタンスa
が存在することを確認する必要があります。
クロージャf
を以下のようにします。
let f = { [weak a] in
gurad let a = a else { return }
print(a)
}
まず、gurad let a = a else { return }
でインスタンスa
の存在を確認し、その後それを使用するようにします。
冒頭のサンプルコードにあるguard let self = self else { return }
も同じことです。
[weak self]
でキャプチャしたself
がクロージャ実行時に存在しているかどうかを確認しています。
##おまけ
キャプチャ時のweak
指定の副作用
すっかりだまされいるようですね。
書いてあることは正しいのですが、実はguard let self = self else { return }
についての説明が足りてません。
guard let xxx = yyy else { return }
はyyy
がOptionalで、yyy
に値がなければreturnして値があればxxx
にその値を入れる、という構文です。
と書きましたが、guard let self = self else { return }
の右のself
はOptional型ではないはずです。
ところが、
var a: A? = A(b: B())
let f = { [weak a] in print(a) }
これを
let a = A(b: B())
let f = { [weak a] in print(a) }
としたとしても、クロージャ内のa
はA
型ではなくOptional<A>
型になります。
これがキャプチャ時のweak
指定の副作用です。
weak
は「(略)解放されたとき値をnil
にせよ」という指示なのでOptional型じゃないと無理なんです。
なので強制的にOptional
型になります。
unowned
「循環参照を避けたいけど、weak
だとOptional
になって面倒くさい! ここでは絶対インスタンスは存在してるのに!!!」
という時に使うのがunowned
です。
weak
修飾子は「インスタンスは参照するがオーナーとしては登録するな。またインスタンスが解放されたとき値をnil
にせよ。」という指示でしたが、unowned
は「インスタンスは参照するがオーナーとしては登録するな」だけの指示を与えます。
nil
にする必要がないため、キャプチャ時に強制的にOptional
型になることはありません。
アンラップを使うことなくそのまま利用できますので便利です。
ただし、当然のことながらunowned
はオーナーを設定しないため利用しようとしたときにインスタンスが解放されてしまっていることがあります。
解放されたインスタンスにアクセスするとプログラムがクラッシュします。
unowned
を使う場合はそのことに十分注意しましょう。