🦁

クロージャの[weak self]はどんなときに使うのか

2021/06/15に公開約1,700字

クロージャの[weak self]はどんなときに使うのか

クロージャの記事を見ると、weak selfがよく使われていますね。
今回の記事で、[weak self]とはなんぞやから、何となくそうゆうことねくらいになってくれると嬉しいです。

循環参照を避けよう

事前知識として、循環参照が良くないということを知っておきましょう。
AオブジェクトがBオブジェクトを参照すると、Bの参照カウントが1になります。
BオブジェクトからAオブジェクトを参照すると、Aの参照カウントが1になります。
これはAとBの間で参照し合っているので、循環参照という現象が起きています。

SwiftはARCというシステムで、オブジェクトをメモリで記憶しています。参照カウントが1以上のオブジェクトを記憶し、参照カウントが0になるとオブジェクトがメモリから破棄されるという仕組みです。
先ほど説明した循環参照では、2つのオブジェクトが参照しあっているため、永遠にそれぞれのオブジェクトの参照カウントが1のままになってしまいます。すると、永遠にメモリから、オブジェクトが解放されなくなってしまい、メモリリークが発生します。そのたびにアプリが止まってしまっては、ユーザー体験が悪くなってしまいますね。

長くなりましたが、循環参照はメモリリークの原因になるので避けましょうということです。

[weak self]とは

さて、ここから本題です。

結論からいうと、[weak self]は、他オブジェクトからselfへの参照を弱参照にして、循環参照を避けるものです。

参照カウントは、弱参照をカウントしません。通常、AとBが参照し合っていると循環参照が起こります。しかし、AからB、またはBからAへの参照を弱参照にすることで、循環参照を避けることができます。

こちらはクロージャを使う一例です。

sample
class Sample {
    let api = API()
    var model: Model?
    init() {
        // トレイリングクロージャという記述方法
	// クロージャが最後の引数の時にのみ使える記述方法
	// クロージャに代入
        api.get { [weak self] result in
            // 受け取った結果を使う
	    self.model = result
        }
    }
}

class API {
    // 引数にクロージャ(completion)をとる
    func get(completion: ((Model) -> Void)? = nil) {
        // API通信
        // デコードして結果を(クロージャ)completionに渡す
        let result = Model()
        completion?(result)
    }
}

struct Model {}

上記したコードはAPI通信をしてModelを受け取る時に、クロージャを使う例を上げています。
APIクラスではAPI通信周りで使う関数を定義しており、それらを他のクラスで使いまわすという感じですね。

この場合、Sampleクラスが、completionクロージャを参照しており、completionクロージャがselfとしてSampleクラスを参照しています。
一見、循環参照が起きそうですが、[weak swlf]を加えているので、completionクロージャからself(Sampleクラス)への参照が弱参照となり、参照カウントされず、結果的に循環参照を避けられていますね。

このように、[weak self]をつけて循環参照を避けていくことでメモリリークを避けましょう。

最後に

私自身、[weak self]をつけ忘れることが多々あるので、記事にしてみました。
勘違いしている点があれば、教えてください!

Discussion

ログインするとコメントできます