# Swiftのメモリ管理

# ARC | Automatic Reference Counting

Swiftは、クラスのメモリ管理に ARC (Automatic Reference Counting) という仕組みを利用しています。

ARCは、新しいインスタンスが作成されるたびに、値を保存するためのメモリを確保します。このメモリには、インスタンスの型に関する情報と、プロパティの値が保持されます。そして、インスタンスが不要になると、ARCは使用されているメモリを自動的に解放します。

インスタンスが不要になったかを判断するために 参照カウンタ という仕組みを利用します。 ARCは、クラスのインスタンスが参照しているプロパティ、定数、および変数の数を追跡し、インスタンスに対して1つでもアクティブな参照が存在する限りは、インスタンスを解放しません。1つも参照がなくなったときに初めてメモリを解放します。

次の例では、クラスのインスタンスを生成して3つの変数に代入しています。クラスは参照型なので3つとも同じ値を参照しています。

class Person {
  var name: String

  init(name: String) {
    self.name = name
  }
}

var person1: Person? = Person(name: "Taro")

// person1を代入
var person2 = person1
var person3 = person1

ここで、Person クラスのインスタンスのメモリを解放するために nil を代入してみましょう。

person1 = nil

person1 に nil を代入しましたが、これではSwiftはメモリを開放しません。なぜなら、person2person3 がまだインスタンスの参照を保持しているからです。

参照カウンタは、1つも参照がなくなったときに初めてメモリを解放するという仕組みでした。なのでインスタンスのメモリを解放するには、残りの変数にも nil を代入する必要があります。

person2 = nil
person3 = nil

この段階で初めて全ての参照がなくなり、Swiftは自動的に使われていたメモリを解放します。インスタンスが解放されたかどうかは次節の デイニシャライザ を使うと判断することができます。

# デイニシャライザ

デイニシャライザ は、インスタンスが解放される直前に呼び出されます。

deinit キーワードを使用してクラス内に定義します。

class Person {
  var name: String
    
    init(name: String) {
        self.name = name
    }
    
    // デイニシャライザを定義する
    deinit {
        print("deinit!")
    }
}

var person1: Person? = Person(name: "Taro")
var person2 = person1
var person3 = person1

インスタンスが解放されたか確認するために、変数全てにnilを代入してみましょう。

person1 = nil
person2 = nil
person3 = nil

/* 実行結果 */
// deinit!

全ての参照がなくなり、デイニシャライザが呼ばれたのが確認できます。

# 強参照の循環 | 循環参照

2つのインスタンス同士がお互いを参照しあっている関係を 強参照の循環 、または 循環参照 と呼びます。インスタンス同士が循環参照を起こしていると、メモリを解放することができません。なぜなら、お互いを参照しあっているので参照カウンタが0にならず、ずっと保持し続けてしまうからです。

次の例では、User クラスと Profile クラスを定義しています。お互いをプロパティとして持ちます。

class User {
  var profile: Profile?
  init() {}

  deinit {
    print("Userクラスのインスタンスが解放されました")
  }
}

class Profile {
  var user: User?
  init() {}

  deinit {
    print("Profileクラスのインスタンスが解放されました")
  }
}

それぞれインスタンスを生成してみましょう。

var user1: User? = User()
var profile1: Profile? = Profile()

この段階では、お互いのプロパティには nil が入っています。

では、生成したインスタンスをお互いのプロパティに代入してみましょう。

user1?.profile = profile1
profile1?.user = user1

プロパティにインスタンスを代入したことによって、下記の図のようにお互いを参照し合う関係になりました。

メモリ管理

そのため、変数に nil を代入してもインスタンスの循環参照が残るのでメモリは解放されません。

user1 = nil
profile1 = nil

メモリ管理

# 循環参照の解決

Swiftは、循環参照を解決するために2つの機能を提供しています。weakunonwned です。

weakunonwned を使うと強参照することなく、他のインスタンスを参照することができます。強参照されないと参照カウンタからカウントされません。そのため、インスタンスは循環参照をせずにお互いを参照することができます。

# 弱参照 | weak

weak を使うと、ARCの参照カウンタにカウントされずに、参照をすることができます。

先ほどの例で、Profile クラスの user プロパティを弱参照させてみましょう。weak キーワードは、プロパティの前に記述します。

class Profile {
  // 弱参照させる
  weak var user: User?
  init() {}

  deinit {
    print("Profileクラスのインスタンスが解放されました")
  }
}

これにより参照カウンタが1つになりました。そのため、変数に nil を代入すれば参照がなくなってメモリが解放されます。

user1 = nil
profile1 = nil

/* 実行結果 */
// Userクラスのインスタンスが解放されました
// Profileクラスのインスタンスが解放されました

デイニシャライザが呼ばれたので、ARCがインスタンスを開放したのが分かります。

# unowned

unowned も同様に、ARCの参照カウンタにカウントされません。weakとの違いは、unowned は nil にならない値を参照させるときに使います。

次の例では、User クラスと Email クラスを定義しています。User クラスは、Email クラスを持たない可能性がありますが、Email クラスは常に User クラスを必要としています。

そのため、User クラスではプロパティをOptional型として持ち、Email クラスでは必須にします。このときに、Email クラスのプロパティを unowned にすると参照カウンタにカウントされずに、User クラスの参照がなくなった段階でメモリを解放することができます。

class User {
  // Optional型として定義する
  var email: Email?
  init() {}

  deinit {
    print("Userクラスのインスタンスが解放されました")
  }
}

class Email {
  // unowned を記述して参照カウンタにカウントされないようにする
  unowned var user: User
  init(user: User) {
    self.user = user
  }

  deinit {
    print("Emailクラスのインスタンスが解放されました")
  }
}

インスタンスを生成して変数に代入します。

var user1: User? = User()

user1?.email = Email(user: user1!)

参照関係は以下の図のようになります。

メモリ管理

参照カウンタは1になるので、変数に nil を代入するとメモリを解放することができます。

user1 = nil

/* 実行結果 */
// Userクラスのインスタンスが解放されました
// Emailクラスのインスタンスが解放されました

プログラムの実行中に、プロパティの値が nil にならないことが明らかなときに unowned を使うのがいいでしょう。

# Debug Memory Graph

Xcodeでは、参照関係を視覚的に確認することができます。

Xcodeの Debug Memory Graph をクリックするとインスタンス間の参照関係を見ることができます。

メモリ管理