【iOS】UserDefaults にバックグラウンドスレッドからデータを格納するとメモリリークした【Xcode/Swift】

iOSアプリのメモリリーク

アプリでメモリリークが発生すると、メモリの空き容量を食いつぶして、いずれアプリがクラッシュしてしまう危険性があります。

iOSアプリのメモリリークの発生原因などについては色々な記事で紹介されていますが、ここでは私が経験した意外なメモリリークの体験を記載させていただきたいと思います。

結論としては、本記事のタイトルの通りで、「バックグラウンドで UserDefaults に対して高頻度にデータを書き込むとメモリリークする」という事象です。

UserDefaults とメモリリーク

ある案件で、私は UserDefaults に大容量のデータを格納しているプログラムに遭遇しました。

本来、UserDefaults にはアプリの設定などの小さなデータを格納することが想定されており、大きな容量のデータを格納することは推奨されておりませんので、これは間違った使い方なのですが・・・

「DBライブラリを導入するほどでもない」という理由で、UserDefaults にそこそこ大きなデータを入れて管理しているプロジェクトもあるのではないでしょうか。

UserDefaults に格納したデータは、そのままディスクへ書き出されるのではなく、一旦メモリ上に蓄積されます。イメージとしては以下のような感じです。

メモリ上のデータがディスクへ書き出されるタイミングはシステム依存だと思われます。
(一応「synchronize」というメソッドで非同期のディスク書き込みを強制できるようですが、公式からこのメソッドは使用しないようにと明言されています。synchronize)

また、「ディスク」と書いている部分の実態はプロパティリスト(plist: XML)です。

UserDefaults はパフォーマンス向上のために、同期的にディスクアクセスしないような仕組みになっているようです。

メインスレッドで UserDefaults にデータを書き込む

まず、メインスレッドで UserDefaults にデータを書き込んだ場合のメモリ使用状態についてみていきます。

0.01秒ごとに UserDefaults にデータを追加しています。このとき、メモリ使用状態は以下のようになります。

メモリ使用量が増えた後、解放される、というのが繰り返されています。

この動きから、「メモリに保持していたデータがディスクに書き出され、メモリが解放される」という挙動になっていると推測できます。

ただ、UserDefaults への全ての操作をメインスレッドで行うため、頻度によっては UI がカクカクしてしまうなどの影響があります。

バックグラウンドで UserDefaults を更新

UI カクカクを改善して UX の向上を図るために、バックグラウンドで UserDefaults にデータ格納するように処理を変更しました。

頻度とデータ量は先ほどと同じですが、格納処理をバックグラウンドで行っています。この時のメモリ使用量は以下のようになりました。

どうしてこうなった・・・

一分間で 1.35 GBもメモリを使用しています。それでいて全く解放される気配がありません。

実際に格納しているデータ以上にメモリが使用されることがわかりました。

Memory Graph でこの時のメモリ領域の状況を簡単にみてみると、かなり大きな容量を持つ大量のArrayが存在していることがわかりました。

これは推測ですが、「◯◯というデータを UserDefaults に格納する」という操作自体がメモリ上に退避されているからだと思います。

実際に、上の状況で「UserDefaults を空にする」という操作を行っても、メモリ使用量は一切改善されなかったため、この操作自体もメモリ上に残ったまま反映されるのを待っている状態なのでしょう。

参考文献

Why Do You Lose Data Stored in User Defaults

iOS 13 - Attempting to store >= 4194304 bytes of data in CFPreferences/NSUserDefaults on this platform is invalid

UserDefaults Limitations and Alternatives

スポンサードリンク