【Swift】強参照、循環参照、弱参照、アンオウンド参照
なんとなくだった部分を下記サイトを参考に簡単に整理してみる。
というかARCがない時に開発してたときは大変そうだなと思った。。
◼︎強参照とは?
ざっくり言うと
例
class SmartPhone { private let producer: String? init(producer: String) { self.producer = producer } deinit { print("SmartPhone deinit") } } var iPhone5: SmartPhone? = SmartPhone(producer: "Steve Jobs") //参照カウント1 var iPhone6: SmartPhone? iPhone6 = iPhone5 //参照カウント2 iPhone5 = nil //参照カウント1 iPhone6 = nil //参照カウント0 ここでdeinitが呼ばれる
しかし、次のようなときは
class SmartPhone { private let producer: String? init(producer: String) { self.producer = producer } deinit { print("SmartPhone deinit") } } class Owner { var smartPhone: SmartPhone? init(smartPhone: SmartPhone) { self.smartPhone = smartPhone } deinit { print("Owner deinit") } } var iPhone5: SmartPhone? = SmartPhone(producer: "Steve Jobs") //参照カウント1 var iPhone6: SmartPhone? var owner: Owner? = Owner(smartPhone: iPhone5!) //参照カウント2 iPhone6 = iPhone5 //参照カウント3 iPhone5 = nil //参照カウント2 iPhone6 = nil //参照カウント1 print("iPhone5: \(iPhone5)") //nil print("iPhone6: \(iPhone6)") // nil print("owner.smartPhone: \(owner?.smartPhone)") //Optional(SmartPhone)
となり、ownerのsmartPhoneは強参照なのでSmartPhoneクラスのdeinitは呼ばれない。
◼︎循環参照とは?
例
//スマートフォンクラス class SmartPhone { private let producer: String var owner: Owner? init(producer: String) { self.producer = producer } deinit { print("SmartPhone deinit") } } //所有者クラス class Owner { var smartPhone: SmartPhone? let fingerPrint: String init(fingerPrint: String) { self.fingerPrint = fingerPrint } deinit { print("Owner deinit") } } var iPhone6: SmartPhone? = SmartPhone(producer: "Steve Jobs") var owner: Owner? = Owner(fingerPrint: "ownerの指紋") iPhone6?.owner = owner owner?.smartPhone = iPhone6 iPhone6 = nil owner = nil
最後にそれぞれのインスタンスにnilをいれてもスマートフォンクラスと所有者クラスのインスタンスがお互いにそれぞれのインスタンスを保持し合っているため、参照カウントが0にならずメモリに残ったままになっている。それぞれのインスタンスにはnilが代入されているので、これらのインスタンスにアクセスする手段もなく、そのうちメモリリークしてしまう。
循環参照を防ぐためには
- 弱参照
- アンオウンド参照
がある。
参照するインスタンスが後からnilになる可能性がある場合は弱参照。
二度とnilにならないのであればアンオウンド参照。
◼︎弱参照
弱参照の場合は先ほどの循環参照の例のSmartPhoneクラスの
var owner: Owner?
を
weak var owner: Owner?
にすれば良い。
こうすれば参照カウントにカウントされずに参照することができる。
アクセスするときはnilチェックで安全に。
if let owner = iPhone6?.owner { print(owner.fingerPrint) }
◼︎アンオウンド参照
ちょっと例が良くないかもだけどスマートフォンは必ず誰かに持たれてるという前提があるする。
//スマートフォンクラス class SmartPhone { var producer: String? let udid: String unowned var owner: Owner init(udid: String, owner: Owner) { self.udid = udid self.owner = owner } deinit { print("SmartPhone deinit") } } //所有者クラス class Owner { var smartPhone: SmartPhone! let fingerPrint: String init(fingerPrint: String) { self.fingerPrint = fingerPrint } deinit { print("Owner deinit") } }
Ownerはスマホを持ちあるかない可能性があるが、スマホには必ず所有者がいるので所有者がnilになることはない。そのためownerをunownedをつけて宣言する。ここではイニシャライザでselfにアクセスする必要があるので、スマートフォンプロパティを「暗黙的なオプショナル型」で宣言する必要がある。