今回も引き続き 強参照循環
を打開するための手段のうちの 弱参照
について、その言語仕様目線での具体的な使い方まわりを確認していきますね。そしてこれまで見てきた 強参照
と比べてどのあたりが違ってくるのか、そんな基礎的なところを眺める回になりそうです。よろしくお願いしますね。
—————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #228
00:00 開始 00:45 弱参照の要点をおさらい 01:23 弱参照で参照されていても解放可能 02:11 弱参照の存在確認 02:42 弱参照はオプショナル型として扱う 06:50 弱参照が解放されたあとは nil になる 08:04 弱参照の解放済みチェック 10:05 nil のニュアンスの違い 11:07 弱参照にするにはオプショナル型が必須 12:46 暗黙アンラップされるオプショナル型も可能 13:58 解放されて nil になったときは didSet は呼ばれない 18:31 Playground だと弱参照で解放されないことも 21:56 Swift Playgrounds だと解放されない現象 26:11 Playground で weak が解消されない場面のおさらい ——————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #228
では始めますね。今日はオートマチックリファレンスカウント(Automatic Reference Counting:ARC)のセクションの中の「循環参照」についてです。このスライドでは「共参照循環」と書いていますが、これを解消するための手段として「弱参照」と「無所有参照」がありますという話です。
まず、「弱参照」とはどういったものかということについてお話しします。これは前回お話ししましたが、重要なポイントとしては「強く保持しない参照」です。この「強く保持する」という動きとは対照的な動きであり、少し理解が難しいかもしれませんが、ARCが自動でメモリを解放する際、その解放を妨げない参照ということです。weak
を設定することで宣言できます。弱参照の間でも解放が可能で、強参照が存在する間は解放できないという点が重要です。
さらに、もう1つ面白い点としては、弱参照が参照しているインスタンスが解放されると、自動的にnil
がセットされるということです。このため、弱参照は必ずオプショナル型で宣言する必要があります。なぜなら、参照先がnil
になる可能性があるからです。
次に、弱参照の具体的な使い方についてです。弱参照はnil
になる可能性があるため、他のオプショナル型と同じように存在確認を行いながら扱います。例えば、weak
を付けた変数は必ずオプショナル型として定義しなければなりません。そのため、オプショナル型として利用することができるのです。これがとても面白いところです。
さて、実際のコードで確認してみましょう。例えば、オブジェクトをインスタンスとして作り、そのインスタンスをweak
で保持する場合を考えます。通常の強参照の場合、インスタンスが保持されている限り解放されません。しかし、weak
参照にするとインスタンスが解放されると、その参照は自動的にnil
になります。
具体的なコード例は以下の通りです。
class Example {
var strongReference: Example?
weak var weakReference: Example?
}
// オブジェクトの生成
var example1: Example? = Example()
var example2: Example? = Example()
example1?.strongReference = example2 // 強参照
example1?.weakReference = example2 // 弱参照
// 例2を解放する
example2 = nil
// 強参照の場合、example1?.strongReferenceはまだ解放されていない
if example1?.strongReference == nil {
print("Strong reference is nil")
} else {
print("Strong reference is not nil")
}
// 弱参照の場合、example1?.weakReferenceは解放されている
if example1?.weakReference == nil {
print("Weak reference is nil")
} else {
print("Weak reference is not nil")
}
このように、弱参照の場合、インスタンスが解放されると自動的にnil
になります。この点が非常に重要であり、ランタイムエラーを防ぐ助けになります。皆さんもこの概念をよく理解し、コードに適用してみてください。以上が、今日の「ARC」と「循環参照」についてのセクションでした。 nil
チェックは、オプショナルバインディングを使うことでインスタンスが存在するかどうかを確認する方法です。例えば、以下のように書くことができます。
if let instance = target {
// ここでinstanceを使用する
}
オプショナルバインディングを使えば、そのブロック内でインスタンスが解放されているかを確認できます。オプショナル型を活用することで、インスタンスを非オプショナル型として扱うことが可能です。
同じように、weak var
も利用できます。weak var
はARC(Automatic Reference Counting)の機能の一部で、参照が消えた場合に自動的にnil
が代入されます。この機能は、メモリ管理の際に非常に便利です。weak
参照を使用すると、参照しているオブジェクトが解放された場合でもプログラムがクラッシュせずに済みます。この原理を理解することは、メモリ管理を正確に行うために重要です。
また、weak var
がオプショナル型でなければならない理由についても触れておきましょう。weak
参照が解放された際にnil
が代入されるため、必然的にオプショナル型でなければなりません。以下のような形で記述します。
weak var instance: SomeClass?
この構文によって、実際に対象が解放されているかどうかをオプショナルチェックで判定することができます。Swiftでは、この機能を活用することでメモリ管理を効率よく行えます。
さらに、!
を使うことでオプショナル型の強制アンラップが可能ですが、これはリスクを伴います。もしnil
をアンラップしようとするとランタイムエラーが発生してしまいます。このため、使用には十分な注意が必要です。以下のように使用します。
let value = instance!
このコードはinstance
が確実に非nil
であることを保証する必要があります。安全なプログラムを書くためには、可能な限り強制アンラップは避けるべきです。
最後に、オプショナル型に対する監視機能についても簡単に触れておきます。weak var
を使った値の監視の場合、新しい値がセットされた場合の動作を指定できます。例えば、以下のように記述します。
weak var target: SomeClass? {
didSet {
if target == nil {
print("ターゲットがnilになりました")
}
}
}
このようなコードを使用することで、対象の値が変更された際の処理を指定できます。
今回の内容は以上です。Swiftのオプショナル型とweak
参照について理解が深まったと思います。メモリ管理や安全なコーディングの参考にしてください。 ターゲットにもう一回値を入れた時、この時にはオールドバリューにしているから分かりにくくなってしまいました。オールドバリューとニューバリューを明確に区別するために、自分自身の変数名をわかりやすくしておきます。こうすれば、何かのオブジェクトから何かのオブジェクトに差し替わったことが分かりやすくなります。
ビッグセットでこの二つを暗示していますが、ここは初期化なのでビッグセットの対象外です。これはイニシャライズフェイズの話で、以前の勉強会で触れた内容ですので、そのときの資料なども参考にしてください。
ここが重要なポイントですが、今、二つのケースがあります。ここを代入オプショナルなどにして、どこかに代入してみます。このインスタンスをnil
にすることによって、ターゲットがnil
になりますね。このnil
はARCによって代入されていますが、この点においてビッグセットは関与しないという仕様です。
ここでターゲットをセットした後に、一度だけビッグセットが呼ばれたことが確認できます。オブジェクトがちゃんとインスタンス解放されるようになっているので、ARCの影響を受けてnil
が入るようになっています。ただし、代入してしまった部分を取り除かないといけません。
ビッグセットが一度も呼ばれていない状態の確認ですが、ターゲットにオブジェクトを入れると、それが解放されていないようで混乱しています。ターゲットにオブジェクトを入れると、ウィーク参照ですが、誰がこのオブジェクトを持っているのか分からない場合があります。
この場合、nil
にならないのは、アサイメントミスの可能性があります。ターゲットはウィーク参照なので、インスタンスが解放されないのはおかしいですね。デバッグプリントを見てみると、nil
になっていない箇所が問題であることが分かります。
プレイグラウンドの影響も考えられますので、別の環境で実行してみることをお勧めします。プレイグラウンドではなく、通常のプロジェクトで確認すると、正しくnil
になって解放されることが分かるかもしれません。
コードを実行してみた結果、オブジェクトが解放されていない場合は、プレイグラウンド特有の問題である可能性が高いです。プレイグラウンドと本来のビルド環境では挙動が異なる場合があるので、通常のプロジェクトで再確認することが重要です。 「イツセットやめてみますか。あれ、コンソールってあるんですか? これかな、一応。これか、あれか。なるほど、イツセット。一応やめてみますかね。これか。でも、実行オブジェクトが入ってる。なるほど、これはどう説明をしたらいいんでしょうか。これをするとだめってことなのかな? これがなければニル(nil)ですね。でも、これでオブジェクトでしょ。ちゃんとオブジェクトが入ってるのが分かるようにしてみますか。
まず、xint
から int
に値を入れて、それで動かします。ここで動かして、ターゲットの内容表示して、インプットの内容表示して、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行、実行。
今みたいなやり方ですね。これでも、ここに直接代入したときはどうなんでしょうか。例えば、8
を入れても、ここはどうでもいいかな。そのときはニルなんですね。なかなか不可解な動きを見せる気がします。ディフィニエイトイニシャライゼーションを使うと、別の場所で代入したインスタンスが残っちゃうという微妙な罠ですね。これが対策として良さそうですかね。
さっきの状態で普通の Xcode のプレイグラウンドで示してみますか。ディフィニットイニシャライゼーションを見る感じですね。ウィーク変数になっていると参照を持たない限りは代入したインスタンスは解放されてしまうという動きをします。
やりたいこととしては、まずインスタンスがあって、ウィーク変数があって、その参照が解除されるかどうかに着目しています。まず A
があって、別の場所で参照されている A
があって、それをウィーク変数に持っています。この段階では当たり前にオブジェクトがありますが、ここで参照を解放すると B
がニルになります。ここまでオッケーなんですが、このときに B
に代入しているのが1行である場合と2行である場合とでオブジェクトが残るかどうかが違います。
例えば、ウィーク変数にいきなりオブジェクトを代入すると、このオブジェクトはどこからも参照されていないので速攻で解放されてニルになるという状況です。同じことを2行に分けて宣言して、その後オブジェクトを代入する場合、ウィークに代入しようとして即座にエロケートされるという風になりますが、解放されないで残ってしまうという感じですね。
こんなふうに説明すると、プレイグラウンドのバグっぽい気がします。あくまでプレイグラウンドのバグなので、普通にアプリ開発するときには全然問題なく基本通りに動くので大丈夫ですが、プレイグラウンドで何か試したいときには一応覚えておいたほうが良いという感じですね。
では、今日は時間になったのでこれぐらいにしておきましょう。また次回は、参照の具合になるかという図が入ったところを見ていく感じにしましょう。今日はこれでおしまいにします。お疲れ様でした。ありがとうございました。