Share Extension…もしかしてメモリリークしてるかもっていう話
ざっくりまとめ。
- Share Extension で実装している共有画面を繰り返し開いていると落ちる
- 詳しく見ていくと、繰り返し表示していると使用中メモリがもりもり増えていく
- 何かが原因でメモリリークしている?
- プレビューを排除するとメモリが増えていくことがなくなって落ちなくなった!
です。
環境
- Xcode v10.1
- Swift 4.2
問題
公開しているアプリツイクラでツイートを繰り返し登録していると
登録画面を表示した段階ですぐさま消える(多分クラッシュしている)という問題が発生していました。
こういう動作を繰り返し行っているとそのうち、登録画面を表示しただけですぐに消えるっていう感じですね。
このツイート登録画面は Share Extension で実装しています。
いちどその問題が発生したあとはしばらくはまた大丈夫になります。
調査
コードを見直しても怪しいところが見つからなかったので、とりあえず最小コードのものを作ってから、
そこから少しずつ肉付けていってアタリをつけようかとおもって、
新規プロジェクトをつくって Share Extension を追加しただけのものを作って、
上記 Gif と同じ様に繰り返し操作してみると...
同じ様に落ちました
ちなみに、その時のコードは下記の通り
class ShareViewController: SLComposeServiceViewController {
override func isContentValid() -> Bool {
return true
}
override func didSelectPost() {
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
override func configurationItems() -> [Any]! {
return []
}
}
ほんとになにもない(笑)
も少し詳しく
落ち方が有無を言わさずな感じというのと、1度発生するとまたしばらくは大丈夫ということから、メモリ周りが気になりみてみました。
ちょっと分かりづらいですけど右肩上がり。。!😇
(スタートの方は40MBぐらい)
そして、コンソールの方には。。。
Got memory pressure notification
が吐かれていて、登録画面を開くたびにそれがどんどん吐かれる...。
クラッシュ寸前には、1回開いただけで3-4行ぐらい同じ warning 文がずらずらっと!
そして落ちたときには、
EXC_RESOURCE RESOURCE_TYPE_MEMORY(limit=XXX,unused=0x0)
というものが。。リーク。。。してますね!?🤔
試しに他のアプリで同じく Share Extension を使って作られているであろう共有画面を同じような操作(Twitter アプリからツイートの共有)してみたら、
そちらも同じく落ちましたね。。。
解決策(回避策といったほうがいいでしょうか)
リークしてるであろうということがわかったとは言え、、
コード見せたように何もしていない標準のママですしどうしようもないのか?とか思いたくもなりますが、
先程他のアプリでも同じくく落ちたとかきましたが、実は落ちなかったものもあるのです。
みんな大好き Slack
ということで、Slack とツイクラで何が違うのか見比べてみました。一目瞭然でした(笑)
プレビューがない!!
上記のGifだと最初 Safari っぽいロゴが表示されてちょっとして Twitter ロゴが表示されているところです。
画像を受け付けている場合は、そこに画像が表示されるはず。。のところ。
Slack のシェア画面にはそれがないのです。
ってことでやってみる
プレビュー機能消せるのですか?
消せます!上記コードに以下を追記します
override func loadPreviewView() -> UIView! {
return UIView()
}
画像を受け付けている場合とかは無いとこまるでしょうが、ツイクラの場合正直いらない(消せるとは思っていなかった(笑))ので、サクッと消してみました。
結果、落ちなくなりました!!!!🎉
ということで、Share Extension つかってて同じ様な感じで落ちるな〜。でもたまにだし、まいっかとか思っていた方はここらみてみると良いかもです👍
追記(2019.02.15)
上記のように単に UIView
を返すだけだと、iPad の時にシェア画面のレイアウトが崩れてしまいます。
テキストを書き込む UITextView
が1行分ぐらいの高さしか確保されないのです。
それを回避する方法として、intrinsicContentSize を override してほしい分の高さを指定して返してあげると、その分の高さが確保されるようになります。
class ShareViewController: SLComposeServiceViewController {
override func loadPreviewView() -> UIView! {
return DummyPreview()
}
}
class DummyPreview: UIView {
override var intrinsicContentSize: CGSize {
return CGSize(width: 1, height: 120)
}
}
こういう感じです。
とはいえこのメモリリーク疑惑自体が、今の環境だと発生しないようなので loadPreviewView を override する必要がなくなってる気がしなくもないですが(笑)
これも同じことを言っていて、ここでは image を設定した UIImageView
を設定することで回避できるとあります。
この image を設定する必要があるということから、 UIView
でも intrinsicContentSize を使えば行けそうだなっていうことに至りました。
感謝感謝 🙏
補足
ただしこの問題、共有機能を有しているすべてのアプリで発生するわけではないです。。
たとえば Safari でページの共有を繰り返しやっても問題ないですし、
グノシーとかでも大丈夫でした。(もしかすると共有するページのURL次第では発生するかもですが)
僕が再現できたのは、
- Twitter アプリでのツイート共有
- Instagram アプリでの投稿の共有
です。この2つのアプリでプレビューがある共有画面を繰り返し呼び出しているとそのうち落ちました。。
渡されるURL(該当サービスのWebサーバ側?)の問題というか相性というか、、そういう話なのでしょうか
なんにせよリークしちゃだめっていう話でもありますが。。
(使用できるメモリが少ない端末ほど発生するまでの繰り返し回数は少ないとは思います)
僕の端末依存なのでは!?疑惑も少なからずありますが、
同じ内容の問い合わせがツイクラに寄せられていたので少なくとも僕だけではないはずです(笑)
(その問い合わせもあってちょっと本格的に調査しだしたという...)