iOSメモリ管理についてのメモ

広告

面接対策でiOSのメモリ管理について調べています。手元のメモ帳にまとめてもいいのだけど、せっかくブログ持っているので載せてしまいます。

仮想メモリについて

iOSには本格的なページング機能はないようだ。仮想メモリ機能はある。

注意:iOSの場合、カーネルが自動的にページをバッキングストアに書き出すことはありません。空きメモリ容量が、あらかじめ計算しておいた閾値を下回った場合、カーネルは、休止中かつ書き換えられていないページを消去するとともに、アプリケーションに対して直接、メモリを解放するよう通知します。

メモリ効率の向上に関するガイドライン p.12 (PDF)

メモリ不足の通知に対処する(iOS)
iOSの仮想メモリシステムはバッキングストアを使わないため、代わりにアプリケーション側が、オブジェクトへの強い参照を適切に削除しなければなりません。空きページ数が、あらかじめ計算しておいた閾値を下回ると、システムは長い期間にわたって書き換えられていないページをできるだけ解放すると同時に、実行中のアプリケーションに対してその旨の通知を送ります。この通知を受け取ったアプリケーションは、警告に応じて適切に対処しなければなりません。すなわち、できるだけ多くのオブジェクトに対する強い参照を削除する必要があります。たとえば、必要に応じていつでも再生成できるデータキャッシュは、消去してしまっても構わないでしょう。

メモリ効率の向上に関するガイドライン p.21 (PDF)

デバイスごとに使えるメモリの量

device: (crash amount/total amount/percentage of total)

  • iPad1: 127MB/256MB/49%
  • iPad2: 275MB/512MB/53%
  • iPad3: 645MB/1024MB/62%
  • iPad4: 585MB/1024MB/57% (iOS 8.1)
  • iPad Mini 1st Generation: 297MB/512MB/58%
  • iPad Mini retina: 696MB/1024MB/68% (iOS 7.1)
  • iPad Air: 697MB/1024MB/68%
  • iPad Air 2: 1195MB/2048MB/58% (iOS 8.x)
  • iPad Pro 12.9: 3064MB/3981MB/77% (iOS 9.3.2)
  • iPad Pro 9.7″: 1395MB/1971MB/71% (iOS 10.0.2 (14A456))
  • iPod touch 4th gen: 130MB/256MB/51% (iOS 6.1.1)
  • iPod touch 5th gen: 286MB/512MB/56% (iOS 7.0)
  • iPhone4: 325MB/512MB/63%
  • iPhone4S: 286MB/512MB/56%
  • iPhone5: 645MB/1024MB/62%
  • iPhone5S: 646MB/1024MB/63%
  • iPhone6: 645MB/1024MB/62% (iOS 8.x)
  • iPhone6+: 645MB/1024MB/62% (iOS 8.x)
  • iPhone6s: 1396MB/2048MB/68% (iOS 9.2)
  • iPhone6s+: 1195MB/2048MB/58% (theoretical, untested)
  • iPhoneSE: 1395MB/2048MB/69% (iOS 9.3)

ios app maximum memory budget

これはどのくらいのメモリを使用するとクラッシュするか調べるツールを用いて測定した結果だそうだ。

iOS Memory Budget Test

新しい機種では搭載メモリの70%ほど使用するまで耐えるものもあるが、概ね搭載メモリの50%ほど使用するとクラッシュすると見ていいだろう。

Advanced Performance Optimization on iPhone OS

WWDCのビデオでメモリ最適化について有用なものがあるようだ。

Advanced Performance Optimization on iPhone OS, Part 2 from WWDC 2010

iOSはデスクトップOSではないのでメモリは潤沢ではない。

Residentメモリ

ページが物理メモリに置かれているときはページがresidentと言う。物理メモリにないページはnonresidentページ。nonresidentページにアクセスするとpage faultが発生してページがresidentになる。

ユーザー空間のメモリはfile-backedとanonymousがある。

residentページはcleanかdirtyに分類される。anonymous memory(mallocなど)はdirty。dirtyなメモリは一度割り当てられると解放かアプリを終了するまでずっとメモリ上に残る。したがって、dirtyなメモリの使用量を下げることが重要。dirtyページを多用するとmemory warningが起きて、やがてメモリ不足でアプリが落ちる。mallocブロックのメモリリークには特に注意する。

file-backedなメモリ(ファイル内容そのまま、ファイルに書き出してしまえば解放することができる)はcleanだが、変更を加えるとdirtyになる。read-onlyならclean。アプリのコードもread-onlyなのでresidentであってもclean。

cleanなページはスワップに書き出すことができるが、メモリプレッシャー(メモリが処理のためにどの程度効率的に使用されているのかを示し、空きメモリ容量、スワップ率、確保されているメモリ、ファイル・キャッシュ・メモリによって決まる)にはなる。

mallocされたメモリはanonymousでファイルに書き出されない。residentであればdirtyである。

Advanced Performance Optimization on iPhone OS, Part 2 from WWDC 2010の5:50あたりから

Advanced Performance Optimization on iPhone OS, Part 2 from WWDC 2010の5:50あたりから

2ページmallocでメモリブロックを確保した。今はnonresidentだが、1バイトだけ書き込んたものが下の図。このときページはresidentになり、dirtyになる。1バイトしか使っていなくてもresident dirtyページになり、このメモリブロックを解放するかアプリを終了するまで続く。

メモリブロックを効率的に初期化する
小さなメモリブロックをmalloc関数で確保した場合、0で初期化されているという保証はありません。memset関数で初期化することも考えられますが、むしろcalloc関数を使った方がよいでしょう。calloc関数は、要求されたメモリを仮想アドレス空間上に確保しますが、初期化は実際に使われる段階で行います。これに対し、memset関数で0に初期化する方式は、仮想メモリシステムに対し、該当するページを物理メモリにマップするよう強制することになります。calloc関数にはほかにも、領域が複数のページにわたる場合に、一括ではなく実際に使う分のみ初期化する、という利点もあります。

メモリ効率の向上に関するガイドライン p.14 (PDF)

VM Tackerで仮想メモリのスナップショットを見られる。

Low Memory Warningを無視しない


Advanced Performance Optimization on iPhone OS, Part 2 from WWDC 2010の10:50あたりから

dirtyなページが増えてくると警告が来る(一番下の黄色い線)。さらに増えると緊急の案内が来る。この時点でバックグラウンドのアプリは終了し、フロントアプリに警告が来る。さらに増えるとクリティカルになりフロントのアプリが落ちる。

再構築できるオブジェクトを解放する、キャッシュを解放する、読み込んだリソースファイルを解放する。

  • UIViewControllerのサブクラスでviewDidUnloadをオーバーライド ← スライドの内容が古い。didReceiveMemoryWarningすべき(後述)
  • appDelegateでapplicationDidReceiveMemoryWarningメソッドを実装する
  • UIApplicationDidReceiveMemoryWarningNotificationを通知に登録する

メモリ警告系のメソッドが正しく動いているかは、シミュレータのSimulate Memory Warningを実行すると手軽に試せる。

Low Memory Warningはアプリがバックグラウンドのときは呼ばれないので、バックグラウンドで動作するアプリでなければ、applicationDidEnterBackgroundや、UIApplicationDidEnterBackgroundNotification通知を受け取ったらリソースを解放する。メモリを事前に解放しておけばバックグラウンドで落とされる可能性が減る。

画像について

UIで使う画像は、UIImageのクラスメソッドimageNamed、そうではない場合にはimageWithContentsOfFileメソッドを使う。

サムネイルを作るのにCGImageSourceが便利(iOS 4の話なので要確認)。

didReceiveMemoryWarning系の実装

UIViewControllerのサブクラスとappDelegateにあるdidReceiveMemoryWarning系のメソッドを正しく実装する。

iOS 5以前では

- (void)viewDidUnload
{
    self.subView = nil;
    self.subViewFromNib = nil;
}

- (void)didReceiveMemoryWarning
{
    self.someDataCanBeRecreatedEasily = nil;
    [super didReceiveMemoryWarning];
}

上記のスライドで解説していたのはこっち。iOS 6以降では

- (void)didReceiveMemoryWarning
{
    if ([self isViewLoaded] && [self.view window] == nil) {
        self.view = nil;
        self.subView = nil;
        self.subViewFromNib = nil;
    }
    self.someDataCanBeRecreatedEasily = nil;
    [super didReceiveMemoryWarning];
}

出典:How to implement didReceiveMemoryWarning?

NSCache

NSCacheはNSDictionaryに似たインタフェースを持つ。スレッドセーフ。能動的な破棄を行うのでメモリに厳しい環境で便利。ダウンロードした画像をオンメモリにキャッシュする際等に有用。

CocoaのNSCacheオブジェクトは、キャッシュ対象を収容する便利なストレージコンテナとして動作し、しかも先に述べたメモリ管理の問題にも対処しています。NSCacheクラスはNSDictionaryクラスと、キーと値の組を保持する、という点で非常によく似ています。しかしNSCacheオブジェクトには、「能動的な破棄」を行う性質があります。すなわち、メモリが潤沢である間は、与えられたデータを「貪欲に」キャッシュします。しかし枯渇してくると、自動的に一部を破棄して、他のアプリケーション用にメモリを解放するのです。その後、再びこのデータが必要になったときには、改めて計算しなければなりません。

メモリ効率の向上に関するガイドライン p.23 (PDF)

SDWebImageでもNSCacheを使用しているらしい。

NSPurgeableData

要調査。

autoreleasepool

重い処理をする際にautoreleasepoolで囲む。

while true {
    autoreleasepool {
        // 処理
    }
}

SwiftのautoreleasepoolはObjective-Cと違ってメソッドになっているので細かいところが異なるらしい。ただSwift 3.0で色々変わった。

メモですので追記あるいは修正するかもしれません。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)