概要
Swiftでコードを書いていると[weak self]っていう記述よく目にすることありますよね?
結論から申し上げますと、
[weak self]は、selfを弱参照にして、クロージャとselfを循環参照をしないようにするために唱えられるものです!
と、このようによく言われますが、僕自身正直なんのことかさっぱりわからない時期が長かったです。僕と同じような状況の方は大勢いるかと思いますので、復習がてらQiitaにまとめようと思いました。
ちなみに、当方も完全に理解しているかは怪しいため、間違っていればご指摘いただけるとありがたいです!
前提条件
前提条件として、[weak self]との関係が深いのがクロージャと循環参照ですので、クロージャに関しては優良な記事がゴロゴロとあるので詳しくはそちらを参照いただければと思います。
また、[weak self]と言うのは、Swiftの文法でキャプチャリストと呼ばれるものを活用しています。
そして公式の解説では、
デフォルトでは、クロージャー式は、それらの値への強い参照を使用して、周囲のスコープから定数と変数をキャプチャします。キャプチャリストを使用して、クロージャで値をキャプチャする方法を明示的に制御できます。
キャプチャリストは、パラメーターのリストの前に、角括弧で囲まれたコンマ区切りの式のリストとして記述されます。キャプチャリストを使用する場合は、inパラメーター名、パラメーターの型、および戻り値の型を省略した場合でも、キーワードも使用する必要があります。
.....『the swift programming language』 より
最下部の参照にURLあり
少し難しく思うかもですが、要するに『デフォルトで強参照しているものを弱参照にするために使っている』と言うことを頭の片隅に入れてください。強参照等に関しては、以下の循環参照でも触れます!
循環参照やクロージャ、コンプリーションハンドラーについて理解がある人は、前提条件を飛ばしてくださって大丈夫です。(なぜデフォルトで値を強参照するのかと言う疑問に関してはそのような仕様になっていると言うことで腹落ちさせましょう。笑)
循環参照とは
循環参照(CircularReference)とは、『2つのインスタンスがお互いに強参照を持ち合う状態』を指します。では、なぜこの状態がよく問題に上がるかというと、そもそも、インスタンスを参照しあう際、デフォルトで強参照という状態になってしまいますので、どちらも強い参照になってしまうということになります。そうすることで、インスタンスのメモリが解放されない状況になります。メモリ解放しないとデータを受け渡しできないような状態になります。
要するに、2つのインスタンス間で参照しあうとで、AがBを参照しつつBがAを参照して、互いに代入しあってループしているような状態になります。(ざっくりですがこんな感じです。)
class A {
let b = B()
}
class B {
let a = A() // error circular reference
}
少々雑ですがざっくりと上記のように、通常の状態で強参照にならないようにするために、「weak」というキーワードを使用することになります。weakとともにプロパティを宣言することでそのプロパティは弱参照になります。その結果、強参照同士でデータが循環し続けることなく、処理を行うことができます。
ちなみに、強参照同士で循環参照に陥ってしまうとメモリリークという問題が発生し、パフォーマンスの低下や最悪の場合アプリが落ちるなんてこともあります。
それだけ、循環参照を意識してアプリ開発しないといけないってことで案外深い話になるわけですね。
活用方法
長々と話しましたが、基本的に[weak self]を使用する場面は限られていて、僕自身はほとんど以下のような関数、いわゆるコンプリーションハンドラーの中でよく使用している印象です。
class Completion {
func completion(completion: @escaping () -> Void) {
// 処理しちゃうぞ
}
}
class Handler {
var number = 0
func addNumber() {
number = 1 + 1
}
let a = Completion()
a.completion { [weak self] _ in
/// プロジェクトによって異なるかと思いますが、こんな感じで宣言します。
gurad let strongSelf = self else { return }
strongSelf.addNumber()
}
}
文法的にあっているかは定かではありませんが、大概@escaping等で非同期処理などを扱うときに使用することが多いですかね?
処理を書いていると、xcodeが警告を出してくれたりエラーを吐いてくれることがほとんどなので循環参照に気づかずにそのまま実装してしまった!なんてことはほとんどないと思いますが、@escaping等を使うときには、[weak self]使うんだなみたいな感じの理解でいいかもしれませんね。
コラム
ちなみに、僕が関わっているプロジェクトでは「weak」がよく使われていますが、weakとは別に「unowned」というキーワードもあるみたいです。興味本位で調べたところ、「weak」と「unowned」の使い分けも「循環参照を招かない参照」と「循環参照を招く参照」で分けられているみたいです。
「循環参照を招かない参照」
- ①クロージャの実行時に、参照するインスタンスが必ず存在する場合は、そもそもキャプチャリストを使わない
- ②クロージャの実行時に、参照するインスタンスが存在しなくてもいい場合は、weakを使用する
「循環参照を招く参照」
- ③参照するインスタンスが先に解放される可能性がある場合は、weakを使用する
- ④参照するインスタンスが先に解放される可能性がない場合は、weakかunownedを使用する
と言う形で場合分けされています。
つまるところ、「unowned」はクロージャ内で使う機会がほとんどないみたいなので、あまり気にしなくてよさそうですね。
さいごに
最近、ようやくクロージャやコンプリーションハンドラーなるものを使う実装に慣れてきたので、いい復習になりました。もっとわかりやすい説明を目指しつつ復習していきたいと思います。
W杯日本代表、お疲れ様でした!
参考資料