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

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

新規登録して質問してみよう
ただいま回答率
85.50%
iOS

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

Swift

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

Q&A

2回答

2447閲覧

iOSアプリにて、UILabelの表示/削除に伴うメモリ使用量を削減したい

KUMAR

総合スコア31

iOS

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

Swift

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

0グッド

0クリップ

投稿2017/11/03 07:41

###前提・実現したいこと
UILabelの追加/削除を繰り返すと、メモリリークのような動作になりメモリ使用量が増えていきます。
・UILabelの削除時にメモリを開放したい
・そもそも使用量を減らしたい

###発生している問題・エラーメッセージ
イメージ説明
上記グラフがUILabelの追加/削除を繰り返した際のメモリ使用量です。
起動直後は43MB程度の使用量でした。
以下グラフに書かれた数字の時点で行った動作です。
0. 180個程度の絵文字を1つずつUILabelに分け、全てのUILabelをviewに追加 → 58MB
0. 1.で追加したUILabelを全てremoveFromSuperview()で削除 → 52MB
0. 1.と2.を繰り返すことでメモリの使用量が増えていく
0. UILabelを全て削除した状態で1分ほど経過しても元のメモリ使用量には戻らず

###該当のソースコード

swift

1import UIKit 2 3class ViewController: UIViewController { 4 let labelFontSize: CGFloat = 20 5 6 // ラベルを追加するコンテナview 7 let labelsContainer = UIView() 8 9 // 絵文字ラベル用 10 let emojiLabelCharacters = "????????????????????☺????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????✨????????????????????????????????????????????????????????????????????✊✌????✋????????????????????????????☝????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????❤????????????????????????????????????????????????????????" 11 12 // テキストラベル用 13 let textLabelCharacters = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよ" 14 15 override func viewDidLoad() { 16 super.viewDidLoad() 17 18 self.labelsContainer.frame = CGRect(x: 0, y: 0, width: self.labelFontSize * 2, height: self.labelFontSize * 2) 19 self.view.addSubview(self.labelsContainer) 20 21 // view中央にラベルの追加と削除を繰り返し行うボタンを配置 22 let button = UIButton() 23 button.setTitle("ラベル表示/削除", for: .normal) 24 button.setTitleColor(.orange, for: .normal) 25 button.addTarget(self, action: #selector(self.toggleLabels), for: .touchUpInside) 26 self.view.addSubview(button) 27 button.translatesAutoresizingMaskIntoConstraints = false 28 NSLayoutConstraint(item: button, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true 29 NSLayoutConstraint(item: button, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 0).isActive = true 30 NSLayoutConstraint(item: button, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 150).isActive = true 31 NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40).isActive = true 32 } 33 34 @objc func toggleLabels() { 35 // ラベルが追加されていれば取り除く 36 if self.labelsContainer.subviews.count > 0 { 37 for v in self.labelsContainer.subviews { 38 v.removeFromSuperview() 39 } 40 } else { 41 // ラベルが追加されていなければ追加する 42 var characters = self.emojiLabelCharacters 43 var c: Character 44 repeat { 45 // 1文字ずつラベルにして貼り付ける 46 c = characters.remove(at: String.Index(encodedOffset: 0)) 47 let l = UILabel(frame: CGRect(x: 0, y: 0, width: self.labelFontSize * 2, height: self.labelFontSize * 2)) 48 l.text = c.description 49 l.font = .systemFont(ofSize: self.labelFontSize) 50 self.labelsContainer.addSubview(l) 51 l.frame.origin = CGPoint(x: 0, y: 0) 52 } while characters != "" 53 } 54 } 55}

###試したこと
iOS11端末(iPhone6)、シミュレーターで試しましたが、どちらもUILabelの追加/削除でメモリ使用量が増えていきました。
文字サイズに比例してメモリ使用量が変化するため、内部的にはテキストを画像化して扱っているのかと推測しました。
そのため、テキストデータとして描画?のような機能があればメモリ削減できるのではと思いました。

###補足情報(言語/FW/ツール等のバージョンなど)
絵文字が簡単に入力できるkeyboard extensionを作成しているのですが、メモリ制限が30MBなのですぐにmemory warningが発生し、一定時間経過後に落ちてしまいます。

なお、キーボード内の絵文字一覧はUICollectionViewでreusableなcellにUILabelを追加して表示しています。

以上、解決方法がありましたらご教授いただければ幸いです。

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

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

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

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

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

guest

回答2

0

https://qiita.com/lovee/items/6fac75134706abbaa653
以前見かけたUIViewとそのサブクラスは作成/削除を繰り返してもとメモリが解放されないのかも?という記事です。
UILabelをできるだけ再利用するようにしてみたらどうでしょうか。

投稿2017/11/06 15:45

u39ueda

総合スコア950

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

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

KUMAR

2017/11/06 16:52

ご回答ありがとうございます。 貴重な情報でした。 deinitが呼ばれる=メモリを開放してくれるものだと思っていましたが、ことUIViewに関しては必ずしもそうでない可能性があるのですね。 私の方ではUIImageは比較的すぐにメモリを開放してくれる印象がありました。 削除された後再度使用される可能性はUILabelの方が高いと判断してiOS側で保持しているのかもしれないですね。(それなら再使用した時に前回以上にメモリを使わないでほしいですが) 画面の切り替え時や終了時でも、UILabelをreusableに保持しておく仕組みも検討します。 また、UILabelを生成した後addSubviewしないで即座に破棄する、というテストケースが抜けていたことにも気づきました。 結果で言うとその場合メモリ使用量は0.1MBも増えなかったのですが、やはり画面に配置すること自体がメモリ上に大きな影響を与えるのかもしれません。 描画サイクルを待つことなくaddSubviewした直後にremoveFromSuperviewを実行するとメモリの使用量は微増に留まりました。
fuzzball

2017/11/07 00:29

この記事、UIViewは‥という割にはUIView単体でテストしていないし、UIImage使ってるし、あまり記事として意味がないのでは。
guest

0

swift

1Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(toggleLabels), userInfo: nil, repeats: true)

上記コードで10分程度、自動でON/OFFを600回繰り返してみましたが、メモリ使用量が10%ほど増加して停止 → しばらくすると元に戻る、を繰り返しましたので、メモリリークは起こっていないと思います。

Instrumentsでも見てみましたがリークは起こっていないようでした。

投稿2017/11/06 01:43

編集2017/11/08 04:20
fuzzball

総合スコア16731

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

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

KUMAR

2017/11/06 14:46

ご検証ありがとうございます。 あまり使ったことはなかったのですが、Instrumentsでメモリリークの検出ができることも初めて知りました。 メモリ使用量の推移はどうだったか、お分かりになるようでしたら教えていただければ幸いです。 30MB超えたら結構容赦なく落とされるので、他にメモリを多用しているところはないか調べつつ調査していきます。
fuzzball

2017/11/07 00:27

「メモリ使用量の推移はどうだったか」というのは回答に書いてありますが? 質問のコードだと起動直後に50MB程度なので、「30MB超えたら」はそもそもダメですが。
KUMAR

2017/11/07 11:31

ご返答ありがとうございます。 すみません、質問内容のように段々使用量が増えていくような動作はあったか、元に戻ったときにメモリ量が起動直後の水準まで開放されていたか等聞きたかったのに変な質問の仕方をしてしまいました。 私の方でも試してみたのですが、頂いたコードは0.5秒ごとのtoggleで、Xcodeのメモリ使用量グラフの時間解像度は1秒なので、ずっとUILabelを表示中の状態のメモリが取得され、timerの誤差が蓄積されていくと取得状態が逆転して、ずっと非表示中のメモリが表示されるような挙動になっておりました。 timerのintervalを1にすると、表示中と非表示中のメモリ使用量が交互に出力されるようになりました。 やはり、微妙ではありますが最初の数十秒は右肩上がりで使用量も増えていくようです。 このあたりはiOSのメモリのライフサイクルとうまく付き合っていく他ないのかもしれません。 起動直後のメモリはkeyboard extensionになるとかなり抑えて(10MBそこそこ)くれるので、今回は単純に増加量にのみ着目しました。
fuzzball

2017/11/07 15:13 編集

回答内の「メモリ使用量が10%ほど増加して停止」は、右肩上がりで段々に増えていきました。「しばらくすると元に戻る」は、起動時の使用量に戻るという意味です。 仮にリークはしていないとしても、一時的にでも30MBを越えてしまうとNGなら、右肩上がりの部分をなんとかしないといけないわけですね。これは、u39uedaさんの回答にあるように、あらかじめ必要なUILabelを生成しておき、再利用することで解決できるのかも知れません。
KUMAR

2017/11/08 01:09

ご回答ありがとうございます。 試しにUILabelを配列に保存してインターバルを3秒で実行してみたのですが、起動直後は2段階に分けて非表示中のメモリ使用量が増加し、その後はUILabelを表示した直後のタイミングでスパイクが発生するというよくわからない動作でした。 https://gyazo.com/f35701397f1a656ad2e08f8dc4a912e1 画面への配置でメモリを消費するなら表示中の3秒間は増加したままであることを期待してましたし、再利用しているのだから2段階で増加して欲しくなかったです。 isHiddenのtoggleだとメモリ使用量は変化しないので、addSubviewはUILabelの生成と同じくらいの負荷がかかるのかな?と思いました。
fuzzball

2017/11/08 03:03

ちょっと試した結果を追記しましたので見てみて下さい。
KUMAR

2017/11/08 04:08

ありがとうございます。 ラベルのインスタンスをループ内で共通にしてしまうと、画面上に絵文字は一つしか表示されなくなってしまうのではないでしょうか?
fuzzball

2017/11/08 04:18

あ‥嬉しさのあまり勇み足ですw やり直します。
KUMAR

2017/11/08 04:32

そうですよねw すみません、よろしくおねがいします。
fuzzball

2017/11/08 08:37

Instrumentsを眺めていると、CoreTextが使っているメモリが残ったままになっているようで、もしかしてフォント関連のキャッシュか何かかなぁと思ったりしていたのですが、そこから特に進展もなく、疲れてきたので降りますw
KUMAR

2017/11/08 09:05

なるほど、フォント… あえてUIImageで表示してみるのも考えられますね。 ありがとうございます、参考になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問