循環参照とは
2つのインスタンス同士がお互いを参照しあっている関係を循環参照と呼びます。インスタンス同士が循環参照を起こしていると、メモリを解放することができません。なぜなら、お互いを参照しあっているので参照カウンタが0にならず、ずっとを保持し続けてしまうからです。
この問題をメモリリークと言い、メモリ領域の圧迫によってパフォーマンスの低下や、場合によってはアプリケーションを終了させてしまいます。
循環参照の例
import UIKit
class ViewController: UIViewController {
var closure: () -> Void = {}
var value = 100
override func viewDidLoad() {
super.viewDidLoad()
closure = { print(self.value)}
}
}
ViewControllerがclosureを参照しており、closureがViewControllerのvalue変数を参照しているので循環参照が起こっています!
参考文献:selfが何が指しているかわからない場合は下記の記事を参考にしてみてください!
XcodeのDebug Memory Graphで参照関係を視覚的に確認してみましょう!
下記の画像からもわかるように、ViewControllerがclosure変数を参照しており、closure変数がViewControllerを参照している。
このように2つのインスタンス同士がお互いを参照しあっているので、循環参照が発生している!
参考文献:XcodeのDebug Memory Graphの表示方法は下記の記事に載っています!
循環参照の解決
参照型は3つの参照方法がある!
1.強参照
デフォルトはstrong
2.弱参照
weak
参照先が存在しない場合に参照しようとされると自動でnilになってる
3.非所有参照
unowned
参照先が存在しない場合に参照しようとされると解放済みへのアクセスとなりクラッシュする
循環参照をするためにweakとunonwnedを使うと強参照することなく、他のインスタンスを参照することができます。
強参照されないと参照カウンタからカウントされません。そのため、インスタンスは循環参照をせずにお互いを参照することができます。
今回はweakによる循環参照の解決方法を詳しく見ていきます!
弱参照weakによる循環参照の解決
import UIKit
class ViewController: UIViewController {
var closure: () -> Void = {}
var value = 100
override func viewDidLoad() {
super.viewDidLoad()
closure = { [weak self] in
if let strongSelf = self {
print(strongSelf.value)
}
}
}
}
[weak self]で弱参照にすることで、ViewController画面がないかもしれない場合を扱うことができる!つまり、画面が開放されていなければ、selfにViewControllerが入っている。
画面が解放されていれば、参照先が存在しないのに参照しようとされるので自動でnilになってくれる。
上記のコードのclosureスコープ内を詳しく見てみましょう!
closure = { [weak self] in
if let strongSelf = self {
print(strongSelf.value)
}
}
このclosureが呼ばれた時にViewControllerの画面がない可能性があるので、if文を用いてself(ViewControllerのインスタンス)があるならば、value変数の値をプリントできます!
[weak self]で弱参照にすることで、if文内のselfも弱参照になるため、循環参照を解決できます。
XcodeのDebug Memory Graphで確認してみても循環参照になっていません。
closureからViewControllerへの参照が弱参照になっています!
closureからViewControllerを参照する際、参照先が存在しない場合に参照しようとされると自動でnilになってくれるので循環参照を防ぐことができます!
参考文献