質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Objective-C

Objective-Cはオブジェクト指向型のプログラミング言語のひとつです。C言語をベースにSmalltalkが取り入れられています。

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Swift 2

Swift 2は、Apple社が独自に開発を行っている言語「Swift」のアップグレード版です。iOSやOS X、さらにLinuxにも対応可能です。また、throws-catchベースのエラーハンドリングが追加されています。

Q&A

解決済

1回答

8206閲覧

【swift】循環参照の見つけ方について

nyancoro

総合スコア75

Objective-C

Objective-Cはオブジェクト指向型のプログラミング言語のひとつです。C言語をベースにSmalltalkが取り入れられています。

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Swift 2

Swift 2は、Apple社が独自に開発を行っている言語「Swift」のアップグレード版です。iOSやOS X、さらにLinuxにも対応可能です。また、throws-catchベースのエラーハンドリングが追加されています。

1グッド

2クリップ

投稿2016/03/16 07:40

swiftを勉強しながらアプリを作っている者です。

メモリ管理を考えた時に循環参照を気にする必要があるかと思うのですが、
これまでそういったことを意識せずにコードを書いていたため、いま見てもどれが循環参照になっているかがわかりません。

見つけるためにはどういった手段が考えられるのでしょうか?
あまり使ったことはありせんが、Instrumentsを地道に見ていくことで見つけられるものなのでしょうか?

教えて頂けますと幸いです。よろしくお願い致します。

keisei_1092👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

後から循環参照を見つけるのは結構難しいと思いますが、循環参照を起こす可能性のある危ないコーディングパターンを意識しておけばよいと思います。私は以下のような点に注意しています。

  • オブジェクトの親子関係(誰が誰を所持しているか)を明確に意識し、子オブジェクトが親オブジェクトをプロパティで保持しないようにする。delegateのように、親オブジェクトとなる可能性があるものを参照する場合は、weak属性にする。
  • ブロックやクロージャ内でselfを参照している場合は、そのブロックやクロージャをプロパティに保持しないようにする。また、ブロックやクロージャをパラメータで受け取るオブジェクトがあれば、そのオブジェクトもプロパティに保持しないようにする。(*1参照) どうしてもプロパティに保持したい場合は、selfをweak属性で参照する。

(*1)例えば、ボタンをタップしたら確認メッセージを出した上で呼び出し元のビューコントローラーに戻るようなモーダルビューコントローラーを作る場合、以下のようにalertControllerをプロパティで保持すると、self→alertController→alertAction→クロージャ→selfの循環参照となり、呼び出し元のビューコントローラーに戻っても、ModalViewControllerは解放されず残ったままとなります。

swift

1import UIKit 2class ModalViewController: UIViewController { 3 var alertController:UIAlertController! 4 @IBAction func pushButton(sender: AnyObject) { 5 alertController = UIAlertController(title: "確認", message: "元の画面に戻ります", preferredStyle: .Alert) 6 let alertAction = UIAlertAction(title: "OK", style: .Default) { (UIAlertAction) -> Void in 7 self.dismissViewControllerAnimated(true, completion: nil) 8 } 9 alertController.addAction(alertAction) 10 presentViewController(alertController, animated: true, completion: nil) 11 } 12}

このケースは、そもそもalertControllerをプロパティで保持する必要はないので、以下のようにローカル変数で定義すればいいだけです。このようにクロージャおよびそれを保持するオブジェクトをプロパティに保持しなければselfの循環参照は発生しません。

swift

1import UIKit 2class ModalViewController: UIViewController { 3 @IBAction func pushButton(sender: AnyObject) { 4 let alertController = UIAlertController(title: "確認", message: "元の画面に戻ります", preferredStyle: .Alert) 5 let alertAction = UIAlertAction(title: "OK", style: .Default) { (UIAlertAction) -> Void in 6 self.dismissViewControllerAnimated(true, completion: nil) 7 } 8 alertController.addAction(alertAction) 9 presentViewController(alertController, animated: true, completion: nil) 10 } 11}

そういうことを考えるよりも、ブロックやクロージャ内でselfを参照する場合は常にweak属性で参照すべきという人もいます。


あまり使ったことはありせんが、Instrumentsを地道に見ていくことで見つけられるものなのでしょうか?

ちょっと古い記事ですが、Instrumentsについては
http://u16suzu.hatenablog.com/entry/2013/11/29/051403
が参考になると思います。
このように、Instrumentsを使えばメモリリークを自動検出して循環参照をわかりやすく図示してくれることがあります。
ただ、完璧ではありません。前述のModalViewControllerの循環参照は自動的には検出してくれませんでした。
でも、メモリ使用状況はよくわかるので、ModalViewControllerの個数が解放されずに増えていく様子はInstrumentsで確認できます。今回紹介した紹介記事やサンプルを使って実際にメモリリークを検出する様子を確認してみることをお勧めします。

投稿2016/03/17 04:21

TakeOne

総合スコア6299

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nyancoro

2016/03/21 16:49

とても丁寧にご教授頂きありがとうございます!! 例の中の >self→alertController→alertAction→クロージャ→selfの循環参照 のところが理解できなかったのですが、もう少し説明して頂くことは可能でしょうか? 未熟なため御手数おかけして申し訳ありませんm(__)m
TakeOne

2016/03/22 02:47

>self→alertController→alertAction→クロージャ→selfの循環参照のところが理解できなかったのですが、もう少し説明して頂くことは可能でしょうか? UIAlertControllerの使い方やクロージャの意味がわからなくて理解できないと言っているのか、それらは理解しているけどサンプルに示したコードでなぜ循環参照になるのかわからないと言っているのか、具体的な疑問点がわからないので説明が難しいのですが、前者でしたら、まずはWebサイトや参考書で使い方や意味を調べてください。 とりあえず、サンプルコードで示した循環参照のチェーンをもう少し丁寧に説明すると、 self(ModalViewController)は var alertController:UIAlertController! のプロパティ定義でUIAlertControllerのオブジェクト(alertController)を保持しています。 alertControllerは、 alertController.addAction(alertAction)で alertAction(UIAlertAction)を保持しています。 alertActionは let alertAction = UIAlertAction(title: "OK", style: .Default) { (UIAlertAction) -> Void in でクロージャを保持しています。 そして、クロージャ内では self.dismissViewControllerAnimated(true, completion: nil) でselfを参照しています。 この場合、 (1)クロージャ内のself参照は強参照で保持されるため、クロージャが解放されるまでselfは解放されません。 (2)クロージャは、alertActionに保持されているため、alertActionが解放されるまでクロージャは解放されません。 (3)alertActionはalertControllerに保持されているため、alertControllerが解放されるまでalertActionは解放されません。 (4)alertControllerは、self(ModalViewController)に保持されているため、selfが解放されるまでalertControllerは解放されません。 (5)selfは、クロージャに保持されているため・・・(1)に戻る で循環参照となります。 このため、dismissViewControllerAnimatedでModalViewControllerの表示を終了しても、selfの循環参照により参照カウンタが0にならないため、ModalViewControllerは解放されずメモリリークとなります。 何度もModalViewControllerの表示・終了を繰り返すと解放されないModalViewControllerがいくつも溜まっていきます。 説明を聞いて理解する前に、まずは問題のサンプルを実行してメモリリークの問題が発生することを実感することをお勧めします。 サンプルのModalViewControllerは、ほとんど何もしないビューコントローラーなのでメモリリークが見えにくいかもしれませんが、ModalViewControllerのプロパティに var array = Array(count: 10000000, repeatedValue: 0) とか追加しておけば、1回ModalViewControllerを表示するたびに80MBずつ使用メモリが増えていく様子がInstrumentsを使わなくても見えると思います。そしてInstrumentsを使えばModalViewControllerが解放されないまま数が増えて行く様子が見えると思います。
nyancoro

2016/03/22 12:22

詳しい説明を頂きありがとうございました! おかげさまで理解することが出来ました。 これまで色々なサイトを読んでなんとなく分かったつもりでいましたが、 今回頂いたサンプルコードを実行することでようやくメモリリークを体感することができました。(百聞は一見に如かずとはこのことですね) 非常に丁寧な解説を頂いて、感謝してもしきれません。 これからまた精進しようと思います。 本当にありがとうございました!m(__)m
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問