Automatic Reference Counting(ARC)は、SwiftおよびObjective-Cにおけるメモリ管理システムであり、各オブジェクトへの参照数を自動的にカウントし、カウンタがゼロになるとオブジェクトを解放します。Apple Swift Documentation、2026によると、ARCはコンパイラに組み込まれ、コンパイル時に動作し、適切な場所にretain/release呼び出しを挿入します。Garbage Collectionとは異なり、ARCは別のコレクタスレッドを必要とせず、アプリケーション実行中に一時停止を発生させません。
重要なポイント
ARC(Automatic Reference Counting)は、AppleがXcode 4.2(2011)でObjective-C向けに導入し、Swiftが継承したコンパイラベースのメモリ管理メカニズムです。手動メモリ管理(Manual Retain-Release、MRR)とは異なり、ARCはretain、release、autoreleaseの呼び出しを完全に自動化し、開発者の介入なしにコンパイル時に挿入します。
ARCはガベージコレクタではありません。動的コード挿入を伴う静的解析です。コンパイラはオブジェクトのライフタイムを解析し、オブジェクトが作成、コピー、またはスコープ外になるポイントにretain/releaseを配置します。結果は決定論的なメモリ解放です:オブジェクトは、遅延や一時停止なしに、参照がなくなった正確なタイミングで削除されます。
WWDC 2011 Session 323によると、MRRからARCへの移行により、Appleアプリケーションにおけるメモリ関連のクラッシュバグが70%削減されました。開発者は手動でretain/releaseのバランスを取る必要がなくなり、リークやdouble-freeエラーのクラス全体が排除されました。
メモリ内の各オブジェクトには参照カウンタ(retain count)があります。オブジェクトが作成されると、カウンタは1に設定されます。新しいstrong参照がオブジェクトを指すと、カウンタが増加し(retain)、strong参照がなくなるとカウンタが減少します(release)。ゼロに達すると、オブジェクトは即座に解放されます。
Swiftコンパイラは、すべての代入でretain/releaseを挿入するわけではありません。最適化のために静的解析を使用します。たとえば、オブジェクトが渡された後に使用されないことが保証されている場合、コンパイラは不要なrelease/retainをスキップできます。この最適化はARC Optimizationと呼ばれます。
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) initialized (retain count: 1)")
}
deinit {
print("\(name) deallocated")
}
}
func testARC() {
let p = Person(name: "Alice") // retain count = 1
let q = p // retain count = 2
// qがスコープ外になる
// retain count = 1
// pがスコープ外になる
// retain count = 0 → deinit
}
この例は、ARCがカウンタをどのように管理するかを示しています:q = pを代入するとカウンタが増加し、qがスコープ外になると減少します。最後のstrong参照がなくなると、デイニシャライザが即座に呼び出されます。ガベージコレクタは待機しません。メモリはすぐに解放されます。
ARCとGarbage Collectionは同じ問題(自動メモリ管理)を解決しますが、根本的に異なるアプローチを取ります。どちらを選択するかは、言語のアーキテクチャを決定します:Swift(ARC)vs Java/Go(GC)。主な違いを見てみましょう。
| 特徴 | ARC(Swift/ObjC) | GC(Java/Go) |
|---|---|---|
| 解放のタイミング | 決定論的:カウンタがゼロになると即座に | 非決定論的:次のコレクションサイクルで |
| 実行の一時停止 | なし(retain/releaseはコンパイル時に挿入) | Stop-The-Worldの一時停止(2~200ミリ秒) |
| オーバーヘッド | 参照ごとのカウンタの増加/減少 | オブジェクトグラフの走査、マーキング、スイープ |
| 問題点 | Retain Cycle(手動解決) | ヒープの断片化、忘れられた参照によるリーク |
| 追加スレッド | 不要 | ガベージコレクタスレッドが必要 |
主要なトレードオフ:ARCは予測可能なオブジェクトライフタイムとゼロの一時停止を提供しますが、開発者はretain cycleを理解し、weak/unownedを正しく選択する必要があります。GCは開発者をこれらの懸念から解放しますが、非決定論的な一時停止と追加スレッドのコストがかかります。
ARCは3種類の参照修飾子を定義しており、それぞれがカウンタとオブジェクトのライフサイクルに異なる影響を与えます。正しい修飾子を選ぶことは、Swiftにおける安全なメモリ管理の基礎です。
Strongはデフォルトの修飾子です。各strong参照はオブジェクトのretain countを1増やします。少なくとも1つのstrong参照が存在する限り、オブジェクトは生き続けます。Swiftのすべてのクラスプロパティとローカル変数はデフォルトでstrongです。strong参照は所有関係を作成します:オブジェクトAはオブジェクトBを所有します。
Weakはretain countを増やさない参照です。weak参照がオブジェクトを指していても、オブジェクトは解放される可能性があります。解放後、weak参照は自動的にnilに設定されます。weak参照は常にvarとしてオプショナル型(?)で宣言されます。特にdelegateパターンで、retain cycleを断ち切るために使用されます。
Unownedは非所有参照であり、weakと同様にretain countを増やしません。ただし、unowned参照は解放後にnilに設定されません。解放されたオブジェクトにアクセスするとクラッシュが発生します。Unownedは、オブジェクトが参照元のオブジェクトと少なくとも同じ期間生存することが保証されている場合に使用されます。典型的な使用例はクロージャ(closures)と、生存期間が保証された親子関係です。
class Customer {
let name: String
var card: CreditCard? // strong
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
class CreditCard {
let number: String
unowned let customer: Customer // unowned — 所有しない
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card \(number) deallocated") }
}
var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// CustomerとCreditCardは両方とも解放 — retain cycleなし
ここでCreditCardはCustomerへのunowned参照を使用しています。Customerはカードを所有し(strong)、カードは顧客を所有しません(unowned)。Customerが解放されると、両方のオブジェクトが解放されます。retain cycleは発生しません。card.customerがstrongだった場合、サイクルが解放をブロックします。
自動化にもかかわらず、ARCは万能薬ではありません。開発者は、内部のメモリ管理メカニズムの理解を必要とするいくつかの典型的な問題に直面します。
Swiftのクロージャ(closures)は、外部変数をstrong参照でキャプチャします。クロージャがクラスプロパティに割り当てられ、selfをキャプチャすると、retain cycleが発生します:クラスがクロージャを保持し、クロージャがselfを保持します。解決策は、weakまたはunownedを使用したキャプチャリストです。
class NetworkManager {
var completionHandler: ((Data?) -> Void)?
var data: Data?
func fetchData() {
completionHandler = { [weak self] result in
guard let self else { return }
self.data = result
self.processResult()
}
}
func processResult() { }
}
キャプチャリスト[weak self]は、クロージャ内でselfへのweak参照を作成します。これにより、潜在的なretain cycleが断ち切られます。Guard let selfは、コードを実行する前にオブジェクトが生きていることを保証します。weak selfは、Swiftにおける非同期クロージャの標準的なプラクティスです。
retain/releaseは軽量な操作ですが、ホットループでの頻繁なカウンタの増減はオーバーヘッドを追加します。Swift 5.9+では、アナライザが安全であることを証明した場合、コンパイラは冗長なretain/releaseを削除する最適化を使用します。ただし、Objective-Cでは、毎秒数百万回の呼び出しがある高負荷シナリオで、retain/releaseが依然としてボトルネックになる可能性があります。
Autorelease Poolは、Objective-Cおよび一部のSwiftシナリオで使用される遅延解放メカニズムです。オブジェクトはプールに配置され、プールが drained になるとreleaseを受け取ります。多くの一時オブジェクト(JSON解析など)を含むループでは、カスタムautoreleasepoolを作成することで、ピークメモリ消費を削減できます。
よくある質問
手動管理(MRR)では、開発者は明示的にretain、release、autoreleaseを呼び出していました。ARCはこれらの呼び出しをコンパイル時に自動的に挿入し、double-freeのリスク、release忘れによるリーク、retain/releaseのバランスエラーを排除します。
ARCはObjective-CオブジェクトとSwiftクラスのみを管理します。C/C++の構造体やポインタにはARCは適用されません。これらのオブジェクトは手動で、またはC++のスマートポインタ(shared_ptr、unique_ptr)を介して管理されます。Core Foundationオブジェクト(CFString、CGColor)もARCの対象外です。
weak — オブジェクトが参照元より先に解放される可能性がある場合(デリゲート、非同期クロージャ)。unowned — オブジェクトが少なくとも参照元と同じ期間生存することが保証されている場合(子が親なしでは存在できない親子関係)。確信がない場合はweakを選択してください。
SwiftのExistential Type(プロトコルを型として使用)は、値を特別なコンテナ(existential container)にラップします。これにより、プロトコル境界でのretain/releaseの数が増加します。Swift 5.7+では、opaque result typeとsomeパラメータがコンテナを排除することでオーバーヘッドを削減します。
Swiftでretain countを読み取るための直接的なAPIはありません。実装の詳細とみなされています。診断には、XcodeのInstruments(Allocations、Leaks)またはメモリデバッガを使用してください。これらのツールは、ライブクラスインスタンスの数と保持チェーンを表示します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。