追記について
追記が増えたので以下の記事を新たに作成しました。こちらと合わせてご参照ください。
Swiftの循環参照問題におけるunownedとweakの使い分けについて
はじめに
クロージャーは定義したスコープの定数や変数をキャプチャすることができます。このキャプチャのおかげでインスタンス変数を定義して、値を保持する必要がなくケースがあるので便利に活用しています。ただキャプチャ値を利用する場合は、循環参照を考慮したコードを記述する必要があります。循環参照に陥ると双方のインスタンスがメモリ上から開放されず、メモリリークの原因になります。
クロージャの循環参照と回避方法
クロージャが循環参照をしている例です。
self
がクロージャを強参照し、クロージャがself
を強参照して循環参照に陥っています。
-
HTMLElement
インスタンスはasHTML
プロパティで() -> String
クロージャを強参照にてインスタンスを保持している -
() -> String
クロージャはHTMLElement
インスタンスをself
として強参照している
コードでは以下のようになります。[unowned self]
というキャプチャリストを記述することでキャプチャしたself
(HTMLElement
インスタンス)をクロージャはself
を強参照しなくなります。
強参照を回避するためにはunowned
とは別にweak
もあります。
unowned
とweak
の違いはキャプチャする変数の値がnilになりえる時(Optional)はweak
、値が常に存在する場合はunowned
になります。
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[unowned self] in // <- Unowned References
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")
}
}
Appleの公式ドキュメントより Swift Automatic Reference Counting
以下わからないころの紆余曲折な内容です
コメント欄の議論の原点なので残しておきます。
こちらの内容をご参照ください。
Swiftの循環参照問題におけるunownedとweakの使い分けについて
unownedとweakの使い分けがわからない
例えば、UIViewControllerのselfをキャプチャする場合は、unowned
とweak
のどちらを記述すればよいのかよくわかりません。
キャプチャする変数の定義にOptionalが明示的に宣言されていればわかりやすいのですが。
クロージャがUIViewControllerをself
としてキャプチャしているところをunowned
で記述するとクラッシュしてしまいます。weak
だとクラッシュしません。self
はUnwrapせずにアクセスするのでOptionalではないと思うのですが、weak
を記述することが正しいのでしょうか。
【追記】このクラッシュの事象はBeta6では改善されているようです。Beta5では発生していました。
実行時エラー (Beta6では改善済)
クラッシュすると以下のようなエラーが出ます。
同じ境遇の方も。。。
今度は swift_unknownRetainUnowned でクラッシュ。 [unowned self]が原因なのは間違いないんですが、これを使ってはいけないケースがあるということなのかな・・・理解が甘い・・・
— akisute (@akisutesama) 2014, 8月 14
コード例
BlocksKitを使っています。self
はUIViewControllerのインスタンスです。
/// unowned は クロージャをセット時にクラッシュ
button.bk_addEventHandler({ [unowned self] (sender: AnyObject!) in
self.navigationController.pushViewController("HOGE", animated: true)
}, forControlEvents: UIControlEvents.TouchUpInside)
/// weak は OK
button.bk_addEventHandler({ [weak self] (sender: AnyObject!) in
if let selfWeak = self {
selfWeak.navigationController.pushViewController("HOGE", animated: true)
}
}, forControlEvents: UIControlEvents.TouchUpInside)
【追記】「初期化後にnilである可能性がある」場合には、unownedではなく、weakを使うべき
UIViewControllerのself
においては、クロージャ実行時にself
がnilになる可能性があるのでweak
を使うのがよさそうです。
UIViewControllerのself
においては、どのケースでどちらを利用すればよいか確認中です。
Beta5ではunowned
を利用するとクラッシュしていたので、weak
を使うべきと判断していたのですが、Beta6ではクラッシュが改善されておりunowned
も記述はできるためです。
判断基準について
コメント欄にて、@tomohisaota さんよりご教授頂きました。ありがとうございます。
@tomohisaota さんのQiita投稿でdelegateの場合ですが、unowned
とweak
について丁寧に解説してくださっているのでそちらも合わせてご参照ください。
参考1 @tomohisaota さんのコメントより
弱参照は常にweakをつかって、nilになる可能性を意識して書けばOKです。
unownedとweakの最大の違いは、オブジェクト破棄時にnilクリアされるかどうかです。
unownedはnilクリアされないので、オブジェクト破棄後にアクセスすると落ちます。
weakはnilクリアされるので、オブジェクト破棄後にアクセスしてもnilになります。
unownedを使うのは、オブジェクトの生成タイミングをきっちり把握していて、絶対にブロック実行時にインスタンスが破棄されていないことが確信できる場合のみです。
その場合であっても、weak使ってはいけないわけじゃないので、迷うならweak一択です。
参考2 @tomohisaota さんのQiita投稿
なぜdelegateはunowned(unsafe)+Implicitly Unwrappedなの?
そもそも「初期化後にnilである可能性がある」場合には、unownedではなく、weakを使うべきです。
キャプチャリストを記述すべきかわからない
【追記】プロパティのget
, set
, willSet
, didSet
はクロジャーではないので、キャプチャリストは不要です。
プロパティのget
, set
, willSet
, didSet
で参照するself
はキャプチャリストが必要かわからないです。
override var description: String! {
get {
return "\(self.title) \(self.url)"
}
}
var title: String! {
didSet {
println(self.description)
}
}
unownedの場合のエラー
weakの場合のエラー
他にも困っている方が。。。
[unowned self] inの使い方がわからない。どうやってもコンパイルエラーになる
— yoshinori.imajo (@yimajo) 2014, 6月 7
そもそもクロージャ内で[weak self]または[unowned self]をやろうとするとエラーになる...XCode Beta1で作ったプロジェクトだから・・・?ちなみにplaygroundでは動作する模様。 #swift
— 借りぐらしのkazuki (@kz_kazuki) 2014, 7月 13
実行環境
- Xcode6 Beta5
- iOS7.1 iPhone5 実機
- iOS8.0 Beta5 iPhone5s シミュレータ
- iOS8.0 Beta5 iPhone5 シミュレータ