Menu

Category

Archive

logo


The Swift Programming Language ~ Automatic Reference Counting [Strong Reference Cycles]

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

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

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

Automatic Reference Counting

前回 までで、ARC の基本とその問題点に関してみてきました。この記事では、プログラマーが気を配らないといけない箇所をみていきます。

3. Strong Reference Cycles の解消 - Weak 参照

Swift には 2 でみた問題を解決するために、2 つの方法があります。それが、weak 参照と unowned 参照です。

weak 参照は、strong 参照ではないので、weak 参照があっても ARC はメモリを解放します。この weak 参照は、その参照が、nil になり得る場合に使用されます。もし、常に nil にならず値を持つような場合には、のちにみる unowned を使用します。先ほどの Apartment の例では、weak が適切です。Apartment クラスはいつでも tenant が nil になる可能性があるからです。weak 参照は値が変わる可能性があるので、Optional 変数として宣言されます。

ARC は、メモリが解放し、その解放したインスタンスに対して weak 参照がある場合、この weak 参照に nil を設定します。私たちは、普通の Optional のように値があるかをチェックできます。

先ほどの Person-Apartmen の例を weak を使い書き直します。

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 }
    weak var tenant: Person?
    deinit { println("Apartment #\(number) is being deinitialized") }
}

Apartment の tenant プロパティが weak として宣言されています。

1
2
3
4
5
6
7
8
var john: Person?
var number73: Apartment?
 
john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)
 
john!.apartment = number73
number73!.tenant = john

そして、Person のインスタンスを解放してみます。

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

この Person に対する strong 参照は、この john = nil で 0 になります。そのため、Person インスタンス は解放されました。このイメージを見ていただければわかりやすいと思います。Person Instance に向かっている矢印は、weak 参照だけです。

そして、Apartment のインスタンスも解放します。

1
2
number73 = nil
// prints "Apartment #73 is being deinitialized"

この Apartment インスタンスは number73 と Person インスタンスのプロパティである apartment から strong 参照がありました。この number73 = nil により、number73 からの strong 参照は消え、上記の john = nil により、Person インスタンスからの strong 参照もなくなっているので、メモリが解放されています。

4. Strong Reference Cycles の解消 - Unowned 参照

unowned 参照も、strong 参照ではないので、unowned 参照があっても ARC はメモリを解放します。この unowned 参照は、その参照が、常に nil にならず値を持つような場合に使用します。そのため、unowned は、Optional としては宣言されません。

先ほどの Apartment の例では、weak が適切です。Apartment クラスはいつでも tenant が nil になる可能性があるからです。weak 参照は値が変わる可能性があるので、Optional 変数として宣言されます。

次は、先ほどとは少し違う例を見ます。Customer と CreditCard というクラスを見ていきます。Customer はクレジットカードを常に持っているかは分かりません。しかし、CreditCard クラスのインスタンスは常に顧客情報である Customer のインスタンスを持っています。つまり、CreditCard の Customer インスタンスを持つ プロパティは unowned として宣言されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { println("\(name) is being deinitialized") }
}
 
class CreditCard {
    let number: Int
    unowned let customer: Customer
    init(number: Int, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { println("Card #\(number) is being deinitialized") }
}

そして、それぞれ初期化していきます。

1
2
3
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

この段階でこのようなイメージです。

そして、解放します。

1
2
3
john = nil
// prints "John Appleseed is being deinitialized"
// prints "Card #1234567890123456 is being deinitialized"

それぞれの strong 参照がなくなるので、両方のインスタンスが解放されます。

5. Strong Reference Cycles の解消 - Unowned 参照 と Implicitly Unwrapped Optional

今までの例では 2 つよくある Strong Reference Cycles のケースを見てきました。

1 つ目は、Person-Apartment のケース。このケースの特徴は、2 つのプロパティ両方とも nil になることが許されてたということです。そのため、weak 参照が使われました。

2 つ目は、Customer-CreditCard の例です。この例では、1 つのプロパティは nil になりえませんでした。そのため、unowned を使用しました。

そして、このセクションでみるのが、2 つのプロパティどちらものが常に値を持つことが期待されている場合です。ここでは、Country と City というクラスを使って考えていきます。国も街も常に値を持ちます。つまり、nil になりません。この例では、unowned 参照をプロパティに設定し、もう 1 つのクラスのプロパティには、Implicitly Unwrapped Optional (!)を設定すると、どちらのプロパティも直接アクセスすることができ、Reference Cycles を防ぐこともできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Country {
    let name: String
    let capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}
 
class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

この 2 つのクラスを初期化するには、Country の Initializer を使用します。通常、Initializer の中から、self は渡せません。以前 みたようにクラスの二段階初期化によるためです。しかし、Country の capitalCity プロパティを an implicitly unwrapped optional property として宣言します。これにより、nil がデフォルト値が設定されます。そして、Country クラスの name プロパティが設定され、初期化されたとみなされた後、self を使用して、City の Initializer に自身を渡すことができます。

この an implicitly unwrapped optional property と unowned の組み合わせで、Strong Reference Cycles を発生させることなく、1 つステートメントで 2 つのインスタンスを作成できます。また、capitalCity は、unwrap (!) することなく、直接アクセスすることができます。

1
2
3
var country = Country(name: "Canada", capitalName: "Ottawa")
println("\(country.name)'s capital city is called \(country.capitalCity.name)")
// prints "Canada's capital city is called Ottawa"

6. Strong Reference Cycles の解消 - Closures

クラスとクロージャの Strong Reference Cycles は、キャプチャリストというものを宣言して解決します。このキャプチャリストは、いつクロージャの本体の中で、参照をキャプチャするのかを定義します。このキャプチャする参照を weakunowned として宣言します。

キャプチャリストの各要素は、クラスへの参照と weakunowned キーワードの組み合わせにより定義されます。この組み合わせは、[] の中に描かれます。複数ある場合には、カンマで区切られます。

1
2
3
4
@lazy var someClosure: (Int, String) -> String = {
    [unowned self] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}

クラスとクロージャが常に一緒に解放されるべき場合には、このキャプチャは unowned として宣言されます。それに対して、プログラムの中で参照が nil になる場合には、weak として宣言されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class HTMLElement {
    
    let name: String
    let text: String?
    
    @lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        println("\(name) is being deinitialized")
    }
    
}

例えばこのように使われます。HTMLElement クラスは、あるクロージャへの参照を持っています。この場合には、unowned として宣言されています。

1
2
3
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
// prints "<p>hello, world</p>"

イメージとしてこんな感じ。

解放すると、

1
2
paragraph = nil
// prints "p is being deinitialized"