Swiftの自動参照カウント(Automatic Reference Counting)

Swiftの自動参照カウント

Automatic Reference Counting(ARC)はSwiftのメモリ管理機能で、日本語にすると「自動参照カウント」です。カウントを「数」とか、もっと固く「計数」としてもいいかもしれません。参照という言葉から推測できるように、参照型に特有の機能です。参照と一言で言っても、実は「強参照」や「弱参照」などの種類があって、これらはARCと密接に関わっています。

ここでは、自動参照カウントとは?その役割は?等を簡単に導入した後、具体例を使って強参照(strong references)の振る舞いについて説明します。また、参照カウントを用いることで発生する循環参照とは何か?、さらに循環参照を解決するための弱参照(weak references)と非所有参照(unowned references)について詳しく説明していきます。

自動参照カウント(Automatic Reference Counting)って何?

ARCは参照型のための機能

自動参照カウント(Automatic Reference Counting)とは、一言で言うと

アプリのメモリ使用量を追跡、管理するための機能

です。

名前だけから機能を推測すると、「インスタンス(instances)を参照している参照カウントを保持する機能」、という限定的な用途を指すようにも思えます。Swiftにおける自動参照カウント(Automatic Reference Counting, 今後ARCと表記)というのは、これから説明するような、インスタンスのメモリ管理(メモリの確保と解放)を引き受ける機能全体を指すようです。

「自動」という言葉からも分かるように、Swiftにおけるメモリ管理は「勝手に(うまいこと)実行される」ので、ユーザはメモリ管理についてあれこれ考える必要はありません。ARCは、クラス(classes)インスタンスが必要なくなったら、自動的にそのクラスインスタンスに使用されていたメモリを解放します。

ただ、自動とは言っても完全ではなく、メモリを管理するために必要な情報を手動でARCに提供しないといけない場合があります。ここでは、そのようなケースについての説明と、どうやってARCがメモリ管理することが可能なのかに関して紹介していきます。

最初に少し強調しましたが、自動参照カウントという名前の通り、これは「参照型」のための機能です。したがって、この機能は値型である構造体列挙型に対しては適用されません。

“NOTE

Reference counting only applies to instances of classes. Structures and enumerations are value types, not reference types, and are not stored and passed by reference.”

抜粋:: Apple Inc. “The Swift Programming Language”。 iBooks https://itun.es/jp/jEUH0.l

ARCの役割(概観)

ARCの基本的な役割は

  1. クラスインスタンスのメモリを確保すること、及び
  2. 不要になったメモリの解放、

の2つです。

ARCはクラスインスタンスが生成される度に、そのインスタンスの情報を記憶するための(ある程度まとまった)メモリを確保します。ARCがメモリに保持している情報は、

です。

不要になったメモリの解放に関してですが、「不要」の判定を間違えた場合は問題が起こります。例えば、ARCがまだ使用中のインスタンスのメモリを解放した場合、そのインスタンスが持つプロパティ(properties)メソッド(methods)にアクセスすることが出来なくなります。そんな状況で、もしユーザがそのインスタンスにアクセスしようとすると、(当然ですが)アプリケーションはまず間違いなくクラッシュします。

そのような問題を避けるために、ARCは今何個のプロパティや変数(または定数)がクラスインスタンスを参照しているか、をちゃんと数えています。この後詳しく見ていきますが、少なくとも1つインスタンスへの参照(厳密に言うと強参照)が残っている限り、ARCがそのインスタンスのメモリを解放することはありません。

具体例で見るARCの働き

ここでは具体例を使って、ARCがどのように働くのかを見ていきます。

//"アカウント"クラス
class Account {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name)を初期化")
    }
    deinit {
        print("\(name)を破棄")
    }
}

中身は公式マニュアルにあるPersonクラスと全く一緒です。

初期化子initを使って、文字列型の定数プロパティnameを初期化しています。終了子(deinitializers)も定義していますが、ただprint(_:)関数を使って文字列を出力しているだけです。

強参照(Strong References)

先程、ARCでは

少なくとも1つインスタンスへの参照が残っている限り、ARCがそのインスタンスのメモリを解放しない

と述べましたが、この参照のことを強参照(Strong References)と呼びます。強参照はそれが残っている限り、絶対にインスタンスのメモリが解放されません。このように、インスタンスと「強く」結びついている参照なので、「強参照」と呼ばれています。

参照カウントに数えられる(含まれる)参照が強参照です。後で出てくる弱参照や非所有参照は、参照カウントの対象外です。

では実際に、先程のクラスを使ってインスタンスを作ってみます。

// nilで初期化
var account1: Account?
// インスタンス生成。初期化子のコール
account1 = Account(name: "アカウント1")

// インスタンスに対する参照を増やす
print("アカウント2と3")
var account2: Account?
var account3: Account?
account2 = account1
account3 = account1

// account3が強参照を保持。メモリは解放されない
print("アカウント1と2にnilを代入")
account1 = nil
account2 = nil

// ここでメモリ解放
print("アカウント3にnilを代入")
account3 = nil

//結果
//アカウント1を初期化
//アカウント2と3
//アカウント1と2にnilを代入
//アカウント3にnilを代入
//アカウント1を破棄

1行目ではオプショナル型(optionals)で宣言しただけなので、変数account1nilで初期化されています。2行目でAccountクラスの初期化子を使って、クラスインスタンスを生成しています。

// インスタンス生成。初期化子のコール
account1 = Account(name: "アカウント1")

変数account1から新しいAccountクラスインスタンスへの強参照が出来ましたので、ARCはこのAccountインスタンスをメモリ上に保持し、勝手に解放することはありません。

試しに、このインスタンスに対する参照を2つ増やしてみます(下図参照)。

// インスタンスに対する参照を増やす
print("アカウント2と3")
var account2: Account?
var account3: Account?
account2 = account1
account3 = account1

[図解]強参照

クラスと構造体のページでも触れましたが、参照型では変数をコピーしてもインスタンスは複製されません。上の図で示したように、増えるのは強参照で、この時点で3つの強参照が同じAccountインスタンスを指しています。

ここで、(最初の参照元である)変数account1account2nilを代入すると、どうなるでしょうか?

// account3が強参照を保持。メモリは解放されない
print("アカウント1と2にnilを代入")
account1 = nil
account2 = nil

実際実行してみると分かりますが、たとえオリジナルの参照元account1nilを代入しても、強参照が1つaccount3に残っていますから、Accountインスタンスのメモリは解放されません。

ARCがメモリを解放するのは、強参照が1つもなくなった時です。つまり、今回の例の場合、account3の強参照が無くなった時初めて、Accountインスタンスのメモリが解放されます。

// ここでメモリ解放
print("アカウント3にnilを代入")
account3 = nil
//終了子が実行され、"アカウント1を破棄"と表示

まとめ

  • SwiftのARC(Automatic Reference Counting, 自動参照カウント)は、アプリのメモリ使用量を管理する機能
  • ARCの具体的な役割は、クラスインスタンスのメモリ確保と、不要になったメモリの解放
  • 強参照では、少なくとも1つインスタンスへの参照が残っている限り、メモリが解放されることはない