LoginSignup
49
48

More than 1 year has passed since last update.

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

Last updated at Posted at 2017-11-21

自己紹介

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] の利用は設計ルール次第。

49
48
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
48