Quantcast
Channel: 初心者タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 21097

【Swift クイズ】そのクロージャ、本当に [weak self] 必要ですか?

$
0
0

この記事は何?

クロージャにおいて [weak self]は本当に必要なのか?
実際のところケースバイケースですが、今回は DispatchQueue.mainを例にしてクイズ・解説をしたいと思います。
ということで、早速クイズです。

環境

  • Xcode Version 11.3.1 (11C504)

クイズ

第一問

次のコードに [weak self]は必要ですか?

DispatchQueue.main.async{[weakself]inguardlet`self`=selfelse{return}// self 使った処理}

第二問

次のコードに [weak self]は必要ですか?

UIView.animate(withDuration:10){[weakself]inguardlet`self`=selfelse{return}// self 使った処理}

いかがでしょうか?
特段の理由なく [weak self]を記述しているのであれば、引き続きクイズ回答・解説をご覧ください。

クイズ回答

第一問

A. [weak self]は必須ではない。処理の内容によっては [weak self]が妥当な場合もある。

第二問

A. [weak self]は不要。

解説

一般的な話として、循環参照を回避する目的で [weak self]を指定する場合がありますが、
上記の処理は、クロージャ実行後に循環参照が解決されるので [weak self]は必須ではありません。

かんたんな実験によって、このことを確認することができます。

classDetail1ViewController:UIViewController{overridefuncviewDidLoad(){super.viewDidLoad()DispatchQueue.main.async{[object=SomeObject()]inobject.doSomething()}// あるいはこういうコード//        UIView.animate(withDuration: 30) { [object = SomeObject()] in//            object.doSomething()//        }}}

ここで、SomeObjectには動作確認のための実装をしておきます。

classSomeObject{init(){log()}funcdoSomething(){log()}deinit{log()}privatefunclog(_function:String=#function){print("🔰🔰🔰\t\(Self.self).\(function)")}}

そうすることで、実行時には次のコンソール出力を確認することができます。

🔰🔰🔰SomeObject.init()🔰🔰🔰SomeObject.doSomething()🔰🔰🔰SomeObject.deinit

doSomething()の実行後に deinitが呼ばれることが確認できると思います。

SomeObject を強参照しているのはクロージャだけなので、
これは、クロージャの実行後にクロージャ自体がメモリから開放されたことを意味しています。

つまり、DispatchQueueUIView.animate[weak self]せずとも、循環参照によるメモリリークは発生しないことを意味しています。
なので、循環参照の回避を目的とした weak キャプチャは不要です。

ただし、非同期処理の実行時にオブジェクトが開放されていても良い場合や、
開放されていることが妥当な場合は、[weak self]を指定することが好ましいと思います。

たとえば、DispatchQueue...asyncAfterが実行される前に、ナビゲーションコントローラから該当のビューコントローラがポップされた場合を考えてみます。
この場合は、次のコードのように [weak self][weak view = view]のような指定を行うことで、無意味な処理をスキップすることができます。

classViewController:UIViewController{overridefuncviewDidLoad(){super.viewDidLoad()// 強参照によるキャプチャを行った場合、// クロージャの実行が完了するまでビューコントローラが開放されることはありません。// ビューコントローラをポップしたあとはビューを操作しても意味が無いので weak キャプチャが妥当です。DispatchQueue.main.asyncAfter(deadline:.now()+10){[weakself]inself.view?.backgroundColor=.orange}}}

DispatchQueue のように、非同期処理の実行後にクロージャが破棄される処理では、次のことに気をつけましょう。

  • weak キャプチャせずとも循環参照は発生しない
  • 必要に応じて weak キャプチャを使うことで無意味な処理をスキップできる

一方で UIView.animateについては、単純にキャプチャリストは不要です。
クロージャ実行後、クロージャを破棄する点では DispatchQueue と同じですが、実はクロージャを同期処理として実行するので、weak キャプチャは不要です。

また、UIView.AnimationOptions.repeatなどを指定してもクロージャの実行は1回きりです。

かんたんな実験によって、この事実の動作確認ができます。

classViewController:UIViewController{overridefuncviewDidLoad(){super.viewDidLoad()// `animations` で指定したクロージャは即実行されるので、weak キャプチャは無意味。//// `completion` で指定したクロージャは、`animations` で指定したアニメーションが停止したタイミングで実行される// 例えば、次のいずれかの処理を行うと `completion` がコールされる。//     - ビューコントローラをナビゲーションコントローラからポップする//     - view.layer.removeAllAnimations() をコールする//// `animations`, `completion` ともに実行は一度きりで、// 実行後にクロージャは開放されるUIView.animate(withDuration:10,delay:0,options:.repeat,animations:{self.view?.backgroundColor=.orange},completion:{[o=SomeObject()]_ino.doSomething()})}deinit{print("🔰🔰🔰\t\(Self.self).\(#function)")}}

このように [weak self]の無いコードを書いても、ナビゲーションコントローラから該当のビューコントローラをポップすることで、ビューコントローラの deinitが呼ばれてメモリリークしないことが確認できます。

まとめ

非同期処理のコールバックハンドラとしてクロージャを扱う場合でも、クロージャの実行後に、そのクロージャがメモリから開放されれば、循環参照が解決されたことになるのでメモリリークは発生しません。

そして、 DispatchQueueは上記に該当するので、[weak self]がなくてもメモリリークしません。[weak self]の有無は、循環参照が引き起こすメモリリーク問題とは切り離して、処理のスキップが必要かどうかで判断しましょう。


Viewing all articles
Browse latest Browse all 21097

Trending Articles