Posted at

Swift vs. C# : Automatic Reference Counting

More than 3 years have passed since last update.

ref


  • ARC = Automatic Reference Counting

  • ランタムでは無くコンパイラがGabage Collectするコードを作成する

  • 同期的にオブジェクトが解放されるようです

  • ARCは "Retain Cycles"に関して自動的に面倒みてくれないので、コンパイラに適切にGCコードいれるようなヒントを与える必要があるようです。


How ARC Works


  • 問題になるのはまだ使用中のインスタンスがARCでdeallocate されようとしたとき


1個インスタンスを作る

  1> class Point{  

2. var X: Int=0
3. deinit{ println("deallocated") }
4. func moveby(x: Int){
5. X += x
6. }
7. }

  8> var p : Point? = Point()

p: Point? = (X = 0)


  • deinit されている

  9> p!.moveby(5) 

10> p = nil
deallocated

 11> p!.moveby(10)

fatal error: unexpectedly found nil while unwrapping an Optional value
Execution interrupted. Enter Swift code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)


参照をつくってみる

Welcome to Swift!  Type :help for assistance.

1> class Point{
2. var X: Int=0
3. deinit{ println("deallocated") }
4. func moveby(x: Int){
5. X += x
6. }
7. }
8> var p1 : Point? = Point()
p1: Point? = (X = 0)
9> var p2 : Point? = p1
p2: Point? = (X = 0)
10> p2 = nil
11> p1 = nil
deallocated

Welcome to Swift!  Type :help for assistance.

1> class Point{
2. var X: Int=0
3. deinit{ println("deallocated") }
4. func moveby(x: Int){
5. X += x
6. }
7. }
8> var p1 : Point? = Point()
p1: Point? = (X = 0)
9> var p2 : Point? = p1
p2: Point? = (X = 0)
10> p1 = nil
11> p2 = nil
deallocated


ARC in Action

Welcome to Swift!  Type :help for assistance.

1> class Person {
2. let name: String
3. init(name: String) {
4. self.name = name
5. println("\(name) is being initialized")
6. }
7. deinit {
8. println("\(name) is being deinitialized")
9. }
10. }
11> var reference1: Person?
12. var reference2: Person?
13. var reference3: Person?
reference1: Person? = nil
reference2: Person? = nil
reference3: Person? = nil
14> reference1 = Person(name: "John Appleseed")
John Appleseed is being initialized
15>
16> reference2 = reference1
17> reference3 = reference1
18> reference1 = nil
19> reference2 = nil
20> reference3 = nil
John Appleseed is being deinitialized


Strong Reference Cycles Between Class Instances


However, it is possible to write code in which an instance of a class never gets to a point where it has zero strong references.

This can happen if two class instances hold a strong reference to each other, such that each instance keeps the other alive.

This is known as a strong reference cycle.



  • PersonとApartmentはお互いの参照を持つように設計してみる

  1> class Person { 

2. let name: String
3. init(name: String) { self.name = name }
4. var apartment: Apartment?
5. deinit { println("\(name) is being deinitialized") }
6. }

8. class Apartment {
9. let number: Int
10. init(number: Int) { self.number = number }
11. var tenant: Person?
12. deinit { println("Apartment #\(number) is being deinitialized") }
13. }

 14> var john: Person? 

15. var number73: Apartment?
john: Person? = nil
number73: Apartment? = nil

 16> john = Person(name: "John Appleseed")

17> number73 = Apartment(number: 73)


  • 相互に参照
    ~~~
    18> john!.apartment = number73
    19> number73!.tenant = john
    ~~~

 20> john = nil

21> number73 = nil

22> john
$R0: Person? = nil
23> number73
$R1: Apartment? = nil

メモリが足りなくなるかもってよ:


Note that neither deinitializer was called when you set these two variables to nil.

The strong reference cycle prevents the Person and Apartment instances from ever being deallocated, causing a memory leak in your app.



  • 相互参照からnil化していけば消える

 21> john!.apartment = nil

22> number73!.tenant = nil
23> john = nil
John Appleseed is being deinitialized
24> number73 = nil
Apartment #73 is being deinitialized


Resolving Strong Reference Cycles Between Class Instances


  • Strong Reference Cycles の問題の解決

1. Weak Reference

2. Unowned Reference


Weak References


  • Apartment の tenant(Person)をweak で宣言

  1> class Person { 

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

 14> var john: Person? = Person(name: "John Appleseed")

john: Person? = Some {
name = "John Appleseed"
apartment = nil
}
15> var number73: Apartment? = Apartment(number: 73)
number73: Apartment? = Some {
number = 73
tenant = nil
}
16> john!.apartment = number73
17> number73!.tenant = john


  • deinit呼ばれる

 18> john = nil

John Appleseed is being deinitialized
19> number73 = nil
Apartment #73 is being deinitialized


  • 逆にApartmentからnilすると、最後に両方deinitされる

 14> var john: Person? = Person(name: "John Appleseed") 

15. var number73: Apartment? = Apartment(number: 73)
16.
17. john!.apartment = number73
18. number73!.tenant = john
john: Person? = Some {
name = "John Appleseed"
apartment = Some {
number = 73
tenant = Some: {
name = ""
apartment = Some {
number = <read memory from 0x13 failed (0 of 8 bytes read)>
tenant = <read memory from 0x1b failed (0 of 8 bytes read)>

}
}
}
}
number73: Apartment? = Some {
number = 73
tenant = Some: {
name = ""
apartment = Some {
number = <read memory from 0x13 failed (0 of 8 bytes read)>
tenant = <read memory from 0x1b failed (0 of 8 bytes read)>

}
}
}
19> number73 = nil
20> john = nil
John Appleseed is being deinitialized
Apartment #73 is being deinitialized


Unowned References

  1> class Customer { 

2. let name: String
3. var card: CreditCard?
4. init(name: String) {
5. self.name = name
6. }
7. deinit { println("\(name) is being deinitialized") }
8. }
9.
10. class CreditCard {
11. let number: UInt64
12. unowned let customer: Customer
13. init(number: UInt64, customer: Customer) {
14. self.number = number
15. self.customer = customer
16. }
17. deinit { println("Card #\(number) is being deinitialized") }
18. }
19> var john: Customer? = Customer(name: "John Appleseed")
john: Customer? = Some {
name = "John Appleseed"
card = nil
}
20> john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)


  • johnをnilにすると、CreditCardの customerが unownedなので、johnのcardもdeinitされる

 22> john = nil

John Appleseed is being deinitialized
Card #1234567890123456 is being deinitialized

ちなみに、johnにcardを設定した段階で、johnを参照するとREPLが死にます。

Bus error: 10


Unowned References and Implicitly Unwrapped Optional Properties

1) Weak, 2) Unowned,


However, there is a third scenario,

in which both properties should always have a value,

and neither property should ever be nil once initialization is complete.


3) unowned + implicity unwrapped optional


In this scenario, it is useful to combine an unowned property on one class with an implicitly unwrapped optional property on the other class.


class Country {

let name: String
let capitalCity: City! //Implicitly Optionally Unwrapped
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
println("Country init:\(name) \(capitalName)")
}
deinit{ println("Goodby Country") }
}

class City {
let name: String
unowned let country: Country // Unowned
init(name: String, country: Country) {
self.name = name
self.country = country
println("City init:\(name) \(country.name)")
}
deinit{ println("Goodby City") }
}

var country :Country! = Country(name: "Canada", capitalName: "Ottawa")
println("\(country.capitalCity!.name)")
country = nil

City init:Ottawa Canada

Country init:Canada Ottawa
Ottawa
Goodby Country
Goodby City

もしもオプショナルじゃないと


let capitalCity: City //Not Optional

./swift6.swift:8:28: error: variable 'self.capitalCity' used before being initialized

self.capitalCity = City(name: capitalName, country: self)

なので、すくなくもオプショナルじゃないと。


Strong Reference Cycles for Closures


  • Strong Reference Cycles はクロージャでも起きる

  • なぜならクロージャも参照型なので


A strong reference cycle can also occur if you assign a closure to a property of a class instance, and the body of that closure captures the instance.


 1> class HTMLElement { 

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


  • textがバインドされるまえにnilするとdeinitされる

 23> var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")

paragraph: HTMLElement? = Some {
name = "p"
text = "hello, world"
asHTML.storage = nil
}
24> paragraph = nil
p is being deinitialized

しかし、nilをするまえに asHTML()を呼んでしまうと textがクロージャに強く参照されてしまって、nilしてもdeinitされない


25> var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")

paragraph: HTMLElement? = Some {
name = "p"
text = "hello, world"
asHTML.storage = nil
}
26> paragraph!.asHTML()
$R0: String = "<p>hello, world</p>"
27> paragraph = nil


  • closure capture list で解決するらしい


Defining a Capture List

クロージャを修飾する


  • [unowned self]

  • [weak someInstance]

リストというからには複数してい可能なんでしょう


Weak and Unowned References

- asHTML() に対して、 selfをownしないこと!と修飾すると self.text が クロージャから弱い参照になるので selfがnilされると deinitが呼ばれるようになる

  2.          

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

25>
26> var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
paragraph: HTMLElement? = Some {
name = "p"
text = "hello, world"
asHTML.storage = nil
}
27> paragraph!.asHTML()
$R0: String = "<p>hello, world</p>"
28> paragraph = nil
p is being deinitialized


GC


C#


参考


Transitioning to ARC Release Notes


Note: For apps targeting the Mac App Store, Apple strongly recommends you replace garbage collection with ARC as soon as feasible, because Mac App Store guidelines (see App Store Review Guidelines for Mac Apps) prohibit the use of deprecated technologies.



Why doesn't Apple Swift adopt the memory management method of garbage collection like in Java?


Swift: ARC vs Flash GC


Why no Reference Counting + Garbage Collection in C#?


Automatic Reference Counting vs. Garbage Collection


ARC provides a method to avoid retain cycles, but it does require some explicit thought and design by the developer. To achieve this, ARC introduces Storage Modifiers that can be applied to object references (such as fields or properties) to specify how the reference will behave. By default, references are strong, which means that they will behave as described above, and storing an object reference will force the object to stay alive until the reference is removed. Alternatively, a reference can be marked as weak. In this case, the reference will not keep the object alive, instead, if all other references to the stored object go away, the object will indeed be freed and the weak reference will automatically be set to nil.



Objective-C ARCによるメモリ管理


MRCがCOM(AddRef/Release)、ARCがATL(CComPtr)と思ってください。