C#
ARC
Swift

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)と思ってください。