Menu

Category

Archive

logo


The Swift Programming Language ~ Automatic Reference Counting

2014-06-23 22:00:00 +0900
  • このエントリーをはてなブックマークに追加

iBooks にある “The Swift Programming Language” の勉強メモ。Objective-C と C を普段書いている自分から、ちょっと馴染みがないものを特にまとめておきます。目次は こちら

今回は、Automatic Reference Counting に関して。

Automatic Reference Counting

Swift はメモリ管理をする際に、ARC を使用します。基本的にメモリ管理は Swift に委ねて問題ありません。

1. ARC 概要

メモリ管理は、 リファレンスタイプであるクラスで特に問題になります。クラスのインスタンスが作成されるたびに、ARC は新しくメモリを割り当てます。そして、必要がなくなった際に、解放され再利用されます。もし、解放された後、そのインスタンスを使用とすれば、アプリはほとんどの場合クラッシュします。

このため、インスタンスが必要がなくなったタイミングを判定する必要があります。ARC は、いくつのプロパティ・コンスタント・変数がそのインスタンスのリファレンスを持っているかを数えています。少なくとも 1 つからでも参照されている場合、そのインスタンスは解放されることはありません。このプロパティ・コンスタント・変数がインスタンスを参照していることを strong reference といいます。

基本的な ARC の動作を例と一緒にみていきます。

1
2
3
4
5
6
7
8
9
10
class Person {
    let name: String
    init(name: String) {
        self.name = name
        println("\(name) is being initialized")
    }
    deinit {
        println("\(name) is being deinitialized")
    }
}

とてもシンプルなクラスです。

1
2
3
var reference1: Person?
var reference2: Person?
var reference3: Person?

3 つの Optional な変数を定義します。

1
2
reference1 = Person(name: "John Appleseed")
// prints "John Appleseed is being initialized

新しいインスタンスが作成されました。init により、初期化されたことがアウトプットされます。この段階で、reference1 から Person クラスのインスタンスに対しての strong reference があると言うことができます。少なくとも 1 つの strong reference があれば、メモリが解放されることはないので、このインスタンスはメモリ上にまだ存在します。

そして、

1
2
reference2 = reference1
reference3 = reference1

この代入により、3 つの strong reference がこの 1 つのインスタンスに作られました。

1
2
reference1 = nil
reference2 = nil

上記のコードで、2 つの strong reference がなくなりました。最初にアサインされた reference1 が削除されても、まだ reference3 からの strong reference が残っているので、このインスタンスへのメモリは解放されません。

ここで、

1
2
reference3 = nil
// prints "John Appleseed is being deinitialized

この段階で、全ての strong reference がなくなりましたので、ARC はメモリを解放します。その前に、deinit が呼ばれるので、その旨がアウトプットされます。

2. Strong Reference Cycles

ARC の問題は、プログラマーがメモリ管理に対して気を配らなければならない場合があることです。それが、Strong Reference Cycles という問題です。これは、インスタンスへの参照(strong reference)が決して 0 にならない状況が考えられるためです。このインスタンスへの参照の数が 0 にならない場合、このインスタンスへのメモリは解放されません。

この問題を解決するために、参照を strong として定義するのではなく、weakunowed と定義することができます。

まずは、具体的に weakunowed を使用する前に、どのような状況でこの Strong Reference Cycles が起こるのか紹介します。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { println("\(name) is being deinitialized") }
}
 
class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { println("Apartment #\(number) is being deinitialized") }
}

先ほどの Person クラスのプロパティの 1 つに Apartment へのインスタンスがあります。また、その Apartment にも Person への参照があります。

1
2
var john: Person?
var number73: Apartment?

これらの参照を保持する変数を定義します。

1
2
john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)

メモリ割り当て、初期化。そして、ついに Person に Apartment を、また、Apartment に Person を関連付けます。

1
2
john!.apartment = number73
number73!.tenant = john

! は、参照を持っている変数からインスタンス自身にアクセスします。C でいう -> でしょうか。

イメージで見ると現在は、こんな感じです。

そして、2 つの変数が持っている strong reference をなくします。

1
2
john = nil
number73 = nil

この際に、deinit に定義されている println は呼ばれません。つまり、メモリ解放は行われていません。まだ 下記のイメージのように Person のインスタンスと Apartment のインスタンスに対する strong reference が存在するからです。この 2 つ変数に nil をセットしたので、基本的にこのインスタンスにアクセスする方法はなく、メモリリークしています。