クロージャ編で、[weak self]を使って循環参照を切る方法を扱った際、ひとつの疑問を保留にしました。weakのほかにunownedもありますが、何が違い、いつ使うのでしょうか。この疑問に答えるには、まずARCが実際にどう動くのか、その仕組みから見る必要があります。
Swift中級シリーズ第1回です。ARCの動作原理、strong・weak・unownedという3種類の参照の正確な違い、そして「weak vs unowned」を選ぶ実務上の基準まで整理します。Objective-C時代のMRC(Manual Reference Counting、手動参照カウント)からARCへ移行した歴史は別の記事で扱ったので、ここではSwiftから見た現在に集中します。
ARCの正体 — ランタイムの清掃係ではなく、コンパイラによる台帳整理
ARC(Automatic Reference Counting)を「Swiftのガベージコレクター」だと思っている人は多いですが、動作の仕組みは根本的に異なります。
ガベージコレクション(GC)は、専用のランタイムシステムが定期的に動作し、「誰も使っていないオブジェクト」を探して片付けます。一方、ARCはコンパイル時に処理が決まります。コンパイラがコードフローを解析し、参照が作られる箇所にretain(カウント +1)、参照が終わる箇所にrelease(カウント -1)の呼び出しを挿入します。カウントが0になった瞬間にdeinitが呼ばれ、メモリが解放されます。
この違いから重要な性質が2つ生まれます。第一に、解放のタイミングが決定的(deterministic)です。GCのように「いつか片付くでしょう」ではなく、最後の参照が消えたまさにその行で解放されます。deinitにリソースの後始末を任せられる根拠です。第二に、プログラムを停止させる清掃フェーズがありません。哲学編第1回で見た「安全のために性能を犠牲にしない」の実例です。
無料ではありません。参照カウントの増減はスレッドセーフである必要があるため、アトミック操作で実行されます。これが参照型に隠れたコストです。Swiftがstructをデフォルトで推奨する理由のひとつでもあります。値型にはこの台帳整理自体がないからです。そして決定的なのは、ARCは循環を解消できないことです。GCは「ルートから到達不能なオブジェクトのグループ」を見つけ、循環していても片付けます。しかしARCでは、互いに数え合っている限りカウントは0になりません。循環参照がプログラマーの責任として残る構造的な理由です。
3種類の参照 — 所有の言語
ARCの世界では、参照は「このオブジェクトを生かしておく責任が自分にあるか」によって3種類に分かれます。
strong(デフォルト)は所有です。 カウントを増やし、自分が保持している限りオブジェクトは生き続けます。属性を付けずに宣言した参照はすべてstrongです。
weakは所有しない観察です。 カウントを増やさないため、残りのstrong参照がすべて消えるとオブジェクトは解放されます。その瞬間、weak参照はランタイムによって自動的にnilへ変更されます。そのためweak変数は必ずオプショナルで、varでなければなりません。「いつでもnilになり得る値」という事実が、オプショナル編で見たように型に刻まれるのです。
unownedも所有しない参照ですが、nilの処理を手放した版です。 weakと同じくカウントを増やしませんが、対象の解放後にアクセスするとnilではなく即座にクラッシュします。その代わりオプショナルではないため、アンラップせずに使えます。
まとめると、strongは「自分が生かしておく」、weakは「いなくなる可能性を知っている」、unownedは「自分より長く生きると確信している」です。
weak vs unowned — 選択基準は寿命関係
では、weakはいつ、unownedはいつ使うのでしょうか。基準は文法の違いではなく、2つのオブジェクトの寿命関係です。
相手が自分より先に消える可能性があるならweakです。 デリゲートが教科書的な例です。ビューがデリゲート(通常はビューコントローラー)を参照する場合、ビューコントローラーが先に解放されるのは十分に正常なシナリオです。そのためデリゲートプロパティは慣例的にweak var delegateとし、使用時はオプショナルチェーン(delegate?.didFinish())で「なければ無視」を表します。nilが正常な状態の関係なのです。
相手が自分と同じか、それ以上長く生きることが構造的に保証されるならunownedです。 教科書的な例はクレジットカードと顧客です。カードは顧客なしには存在できず、カードが生きている間、顧客は必ず生きています。unowned let customerが正確な表現です。オプショナルではないため、使うたびにアンラップする煩わしさがなく、letで宣言して不変性も守れます。
この基準を強制アンラップ!の基準と重ねると、一貫性が見えてきます。unownedは参照版の!です。「ここでnilなら設計が壊れている」という宣言であり、その確信が外れたとき、静かな誤動作ではなくクラッシュで即座に知らせます。確信がなければweakを使います。実務では「迷ったらweak」が主流です。weakのコスト(オプショナル処理とわずかなランタイムオーバーヘッド)のほうが、クラッシュのリスクより安いからです。unownedは、寿命の保証がコード構造から明確に読み取れる場所だけで慎重に使う道具だと考えるのが安全です。
クロージャキャプチャでも同じ基準が適用されます。[weak self]が基本の選択肢なのは、クロージャが実行される時点でselfが生きている保証がほとんどないためです。一方、クロージャとselfの寿命が結び付いている場合、たとえばlazyプロパティの即時実行クロージャでselfをキャプチャするときは、unownedが正当化されます。プロパティが生きているなら、selfも生きているからです。
ツールで確認する — 勘ではなく計測
循環参照はコードレビューだけではすべて見つけられません。3つの確認ツールを知っておけば、「たぶん大丈夫」が「確認した」に変わります。
deinitログが最も安価なツールです。 画面を閉じたのにビューモデルのdeinitが記録されなければ、どこかにリークがあります。開発中に疑わしいクラスへprintを1行入れる習慣だけで、ほとんどの循環を早期に発見できます。
XcodeのMemory Graph Debuggerは循環を図で表示します。 実行中にデバッグバーのメモリグラフボタンを押すと、現在のヒープ上のオブジェクトと参照関係がグラフで表示されます。本来解放されているはずのオブジェクトが残っていれば、矢印をたどって何が保持しているか追跡できます。リークが疑われるオブジェクトが紫色の感嘆符で表示されるのも手がかりです。
InstrumentsのLeaksテンプレートは時間軸で計測します。 画面を開いて閉じる操作を繰り返し、メモリが階段状に増えるかを見るため、リリース前の定期点検に適しています。
ちなみに、循環の常連容疑者は決まっています。プロパティに保存されるクロージャ(クロージャ編)、デリゲートをstrongで宣言してしまうミス、NotificationCenterやタイマー関連の登録解除漏れ(NSTimer編)です。新しいコードをレビューするとき、この3か所だけ確認しても循環の大半をカバーできます。
まとめ
- ARCはランタイムの清掃係ではなく、コンパイル時にretain/releaseを挿入する仕組みです。解放は決定的でGCによる一時停止もありませんが、循環を自力で解消することはできません。
- 参照は所有の言語です。strongは所有、weakはnilの可能性を知る非所有、unownedは相手がより長く生きるという確信に基づく非所有です。
- 選択基準は寿命関係です。相手が先に消える可能性があるならweak、構造的に長く生きるならunowned、迷ったらweakです。
- unownedは参照版の強制アンラップです。コードから確信が読み取れる場所だけで使います。
- deinitログ、Memory Graph Debugger、Instruments Leaksで、勘ではなく計測によって確認します。
次回は中級シリーズ第2回、ジェネリックです。山括弧内のTが型安全とコード再利用をどう同時に実現するのか、where句がいつ必要なのかを整理します。

