#意味のある検証かどうかは置いておいて…
私はメモリに対して
いらなくなったらきちんと解放してくれるのだろうか・・・
使いたい時に使えるようにきちんと持っているのだろうか・・・
という漠然とした2つの恐怖心を持ってます...
幸いメモリの確保と解放はinit
とdeinit
にprint文を埋め込んで追うことができるようなので、幾つかのプログラムを検証して理解を深めていきたいと思います。
なお、deinit
は
変数にnil
が代入されるもしくは
変数のスコープ外に処理が移動すると動き出すようなので
今回の動作検証ではnil
を代入してみました。
##1.最も単純なケース
class A {
init(){
print("A init")
}
deinit{
print("A deinit")
}
}
var a:A? = A() // "A init"
a = nil // "A deinit"
これは予想通り。まだまだ理解できる...!
##2.他の変数が参照しているケース
class A {
// 省略
}
var a:A? = A() // "A init"
var a2:A?
a2 = a // a2もaを参照!!
a = nil // 何も起きない...
a2 = nil // "A deinit"
a = nil
のところでA deinit
と出ない!
これはARCのおかげで複数の変数から参照されている時は、メモリを解放せず残してくれるようです。
##3.インスタンス変数に別クラスのインスタンスを持っているケース
class A {
var b:B? = B()
// 省略
}
class B {
// 省略(initで"B init" deinitで"B deinit")
}
var a:A? = A() // "B init"
// "A init"
a = nil // "A deinit"
// "B deinit"
init
はB⇨Aの順に行われて、deinit
は反対にA⇨Bの順に行われる模様。
入れ子になっている時は、上位のインスタンスを解放すれば、ぶら下がっているインスタンスも自動的に解放されていくらしい。
##4.インスタンス変数を外から参照しているケース
class A {
// 省略(3.のケースと同じ)
}
class B {
// 省略(3.のケースと同じ)
}
var a:A? = A() // "B init"
// "A init"
var b2:B?
b2 = a!.b // b2にaのインスタンス変数bを参照させる
a = nil // "A deinit"
b2 = nil // "B deinit"
少し複雑になってきました。
3.の例でa
の解放によって自動的に解放されたb
は、今回外部のb2
という変数から参照されているため残り続けています。
2.の例では複数の参照がある場合にメモリを残しておいてくれることにありがたみを感じていましたが、「上位のインスタンスをnil
にすればメモリ解放は万事OK」なんて思ってると痛い目を見そうです。これは意外とくせ者かも...
##5.インスタンス変数にDictionaryを使ってみたケース(なんとなく検証)
class A {
var dict : Dictionary = Dictionary<String, AnyObject>()
init(){
print("A init")
let b:B? = B()
let c:C? = C()
self.dict["b"] = b
self.dict["c"] = c
}
deinit{
print("A deinit")
}
}
class B {
// 省略(3.のケースと同じ)
}
class C {
// 省略(initで"C init" deinitで"C deinit")
}
var a:A? = A() // "A init"
// "B init"
// "C init"
var b2:B?
b2 = a!.dict["b"] as? B // b2にaのインスタンス変数bを参照させる
a = nil // "A deinit"
// "C deinit"
b2 = nil // "B deinit"
やっぱり外で参照しているインスタンスは残っていました。
他にも例が思いついたら書き足していこうかと思います。