Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
43
Help us understand the problem. What is going on with this article?
@hs7

[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

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


[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]設計ルール次第。

43
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
hs7
ガンダムの芸術家、アプリエンジニア

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
43
Help us understand the problem. What is going on with this article?