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 は、キャプチャリストというものを宣言して解決します。このキャプチャリストは、いつクロージャの本体の中で、参照をキャプチャするのかを定義します。このキャプチャする参照を weak
や unowned
として宣言します。
キャプチャリストの各要素は、クラスへの参照と weak
か unowned
キーワードの組み合わせにより定義されます。この組み合わせは、[]
の中に描かれます。複数ある場合には、カンマで区切られます。
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" |