##自己紹介
iOSアプリエンジニア
swift歴約3年。kotlin歴約8ヶ月。
Oisixでは、主にiOSアプリ、サーバーAPIなどの開発を担当しています。
##概要
- 循環参照回避などでよく使われる。
- kotlin(というかJavaだと)WeakReferenceが近いかも思います。
- kotlinとswiftを比較した時に、[weak self]はswift特有だと思ったのと、使い所を自分でも再確認したかったため、今日発表してみようと思った
##循環参照とは
class Person {
var closure: (()->Void)?
func someFunc() {
self.someFuncAsync {
print(self)//<ここで、closureがselfを強参照してしまい、解放されなくなる>
}
}
private func someFuncAsync(_ closure: @escaping (()->Void)) {
self.closure = closure
}
deinit {
print("deinit") //解放されないので、ここがコールされない
}
}
var person: Person? = Person()
person?.someFunc()
person = nil //<~~> の部分で、closureがpersonをstrongCaptureしているため、ここでnilにしても、解放されない。
PlaygroundPage.current.needsIndefiniteExecution = true // playground上での非同期処理をできるようにする( https://developer.apple.com/documentation/playgroundsupport/playgroundpage/1964501-needsindefiniteexecution )
##解決策
※他にも色々方法あり
class Person {
var closure: (()->Void)?
func someFunc() {
self.someFuncAsync {[weak self] in
print(self)//selfが弱参照のため、ここがコールされる前にselfが解放されていたら、この時点でself == nil。[unowned self]でもよいがそれだと、self == nilだった時にクラッシュする
}
}
private func someFuncAsync(_ closure: @escaping (()->Void)) {
self.closure = closure //closureをメンバ変数で持たないでも回避可能
}
deinit {
print("deinit") // person = nilで(どこにも参照されなくなるんので)コールされる
}
}
var person: Person? = Person()
person?.someFunc()
person = nil
PlaygroundPage.current.needsIndefiniteExecution = true
これ以外にも、循環参照パターンはいろいろあります。
詳しくは下記の通り。
##[weak self]について
- closureでよく使われる。
- [~~]Swiftのキャプチャリストという機能。
- self以外や[unowned self, weak delegate = self.delegate]という感じにも使えます
(中の実装が見れない)iOS標準SDKのclosureを持つメソッドでは[weak self]しないと、循環参照になるのか?
DispatchQueue.main.async {
print(self)
}
UIView.animate(withDuration: 1) {
print(self)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 60) {
print(self)
}
URLSession.shared.dataTask(with: URL(string: "https://www.google.co.jp")!) { (data, res, err) in
print(self)
}.resume()
ならなかった。少なくとも今あげたものは。。。
じゃあ、さっきのは[weak self]しなくてよいのか?
そういうわけでもない。
例えば、下記の様なパターンで、selfがViewControllerなどの場合に、60秒後はViewController.dismiss後だったら、BADACCESSなどの予期しない実行エラーが発生します。
例えば、下記の様なパターンでselfがViewControllerだった場合でDispatchQueue.main.asyncAfter()のクロージャ内で何かself(ViewController)に対して処理することを想定していたときに、それがViewController.dismiss後であったら、それは予期しない動作となることもあります。
※ @furuyan さんのご指摘により修正しました。ご指摘ありがとうございます!
DispatchQueue.main.asyncAfter(deadline: .now() + 60) {
print(self)
}
このようにDispatchQueue.main.asyncAfter()のクロージャ内ではselfを強参照してもメモリリークにはならない。でも、想定した動作と異なる動作となる場合があります。
##それを防止するよくあるパターン
DispatchQueue.main.asyncAfter(deadline: .now() + 60) { [weak self] in
guard let `self` = self else { return }
print(self)
}
##guardするときの変数名どうするかもいろいろある。
guard let self = self else { return }
guard let weakSelf = self else { return }
guard let strongSelf = self else { return } // Alamofire/Alamofire のパターン
##まとめ
- クロージャ内は常に
[weak self]
(や[unowned self]
)である必要はない。 -
[weak self]
かself
を使うか迷ったら、[weak self]
の方が安全。 -
[unowned self]
の利用は設計ルール次第。