post Image
[weak self]の使いどころ swift/kotlin勉強会#2用

自己紹介

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

解決策

※他にも色々方法あり

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

これ以外にも、循環参照パターンはいろいろあります。
詳しくは下記の通り。

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID52


[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である必要はない。
  • [weak self]かselfを使うか迷ったら、[weak self]。
  • [unowned self]設計ルール次第。


『 Swift 』Article List