スキップしてメイン コンテンツに移動

Objective-Cのメモリー管理

最近はJavaをまずプログラム言語として始める人が多いだろうから、Objective-Cを使いだした際メモリー管理でつまずくことが多いだろう。わたし自身もすっかりJavaな人になっていたので同様に最初のころハマった。一応メモリー管理についてまとめておこう。

Objective-Cでは10.5からは今時の言語っぽくGabage Collectionが導入されので、Java同様あまりメモリー管理を気にする必要はなくなった。だが、パフォーマンスの問題からなのかXCodeのデフォルトの設定ではオフになっていたりするので、一応レガシィなメモリー管理のお手前も知っておこう。

リファレンスカウンタ、retain/release

さすがに大昔のC++のようにnew/deleteで自身で管理ということはなくて、リファレンスカウンタという割と一般的な手法でメモリー管理はなされている。これはある変数について参照が増えるたびにカウントアップ、参照がなくなるごとにカウントダウンしていき、参照カウンタがゼロになった時点でオブジェクトが削除されるという管理方法。Objective-Cの場合、この仕組みが言語でなくCocoaのライブラリレベルで実現されている。

リファレンスカウンタは生成時点で1に、その後 retainメッセージを送るごとに一つ増え、releaseするごとに減る。オブジェクトにretainCountというメッセージを送ると現在の参照カウンタを返してくれる。以下のようなコードでテストすると、


    ObjectA* objA = [[ObjectA alloc] init];
    ObjectA* obj_copy;
    NSMutableArray* array1 = [NSMutableArray array];
    printf("After initializing        : %i\n,[objA retainCount]);
    obj_copy = objA;
    printf("After assignment          : %i\n,[objA retainCount]);
    [objA retain];    printf("After retain              : %i\n,[objA retainCount]);
    [array1 addObject:objA]; 
   printf("After adding to array     : %i\n,[objA retainCount]);
    [array1 removeAllObjects];
    printf("After removing from array : %i\n,[objA retainCount]);
    [objA release]; 
   printf("After release             : %i\n,[objA retainCount]);

結果は、

    After initializing        : 1
    After assignment          : 1
    After retain              : 2
    After adding to array     : 3
    After removing from array : 2
    After release             : 1

となる。
生成した際にリファレンスカウンタは1になる。別の変数に代入した際にリファレンスカウンタを増やしたければ代入するだけではだめで明示的にretainをかけてやる必要がある。NSMutableArrayに加えたときリファレンスカウンタが増えるのはオブジェクトを集合に追加する際retainをかけてくれているから。Cocoaで提供されているコレクションフレームワークは内部でretainをかけており、removeする際releaseをかけているので注意が必要だ。

AutoreleasePool

しかし、こうやってすべてのオブジェクトを管理するのは面倒だというので、AutoRelasePoolという仕組みが用意されている。このプールにオブジェクトを登録しておけば、リファレンスカウンタに関係なくプールが存在する間はオブジェクトのインスタンスも保証され、Poolの削除とともにオブジェクトも破棄されるという仕組み。Cocoaのクラスはこの仕組みが前提になっているケースが多いので、Foundationのクラスを使うターミナルのプログラムでも以下のようなコードがメイン関数に追加されている。

    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];    // User code    ObjectA* objA = [[[ObjectA alloc] init] autorelease];    .....    [pool drain];

このように プールを作成しオブジェクトにautoreleaseメッセージを投げることでプールに登録することができる。オブジェクトはプログラムでreleaseせずとも[pool drain]で破棄される。仕組み的には[pool drain]で登録されたオブジェクトに一斉にreleaseを投げているだけのようだ。逆に言うとプールに登録したオブジェクトといえど、リファレンスカウンタで制御されていてretain/releaseの回数のつじつまはあっていなければならない。「リファレンスカウンタに関係なく」と書いたが、実際にはプールに登録したオブジェクトに関しては「プログラム上でリファレンスカウンタに関係してはならない」が正解か。

また、NSAutoreleasePoolは複数生成することができる。


    NSAutoreleasePool* pool1 = [[NSAutoreleasePool alloc] init];
    printf("Pool1 is created\n");
    ObjectA objA = [[ObjectA alloc] initWithString:@"objA"];
    [objA autorelease];    printf("Pool2 is created\n"); 
   NSAutoreleasePool* pool2 = [[NSAutoreleasePool alloc] init];
    ObjectA objB = [[ObjectA alloc] initWithString:@"objB"];
    [objB autorelease];    printf("Pool2 is drained\n");
    [pool2 drain];    printf("Pool1 is drained\n"); 
   [pool1 drain];

結果は

    Pool1 is created.    Pool2 is created.    Pool2 is drained.    objB is released.    Pool1 is drained.    objA is released.

つまりオブジェクトは直近に生成されたプールにより制御される。一つのプールに依存すると、プログラムが終了するまで多量のインスタンスを抱えることになりかねない。多くのメモリーを消費するクラスや多量のインスタンスを生成する処理では、対象のインスタンスを使用するスコープでNSAutoreleasePoolを生成し不要となったら破棄するのがベター。

Objecitve-Cにおけるretain/release/autoreleaseの方針

実は、このAutoreleasePoolがあるので最初にObjective-Cを書こうと思ったときこのメモリー管理で訳が分からなくなる。上記は簡単な例で説明したが、通常はオブジェクトはインスタンス変数に別のオブジェクトの参照を持っておりそれぞれのライフサイクルが異なっているのは普通で、しかもそれぞれ自身でretain/releaseをかけていたり、AutoreleasePoolで管理されていたりと管理方法もバラバラになってしまう。で、結局retain/releaseのバランスが崩れて、二重解放してしまったり解放されないオブジェクトができたりする。

いくつかの実験とWebサイトなどの情報をまとめると、以下のような方針がよいようだ。

  1. 自身が生成したオブジェクトは自分で解放する。つまり、alloc-initでオブジェクトを生成したメソッド、またはクラスは自身の責任はreleaseをかけるか、生成時にautoreleaseを投げておく。
  2. CocoaのFrameworkでは、クラス名が先頭についた生成メソッドはautoreleaseがかかっている。例えば、NSArrayにおける+arrayWithObjectsや、NSStringにおける+stringWithCStringなど。これらについては、積極的にこれらの生成メソッドを使用しalloc - init***で生成しない。
  3. 同様に自作のクラスについては、CocoaのFrameworkに習いなるべく+クラス名の生成メソッドを用意しその中でautoreleaseをかけるなどして、自身のオブジェクトのライフサイクルに関しては自身で責任を取る。
  4. インスタンス変数については、そのオブジェクトの由来に関わらずretainをかけ、deallocの中でreleaseを入れる。こうすれば、例えばAutoreleasePoolに登録されているオブジェクトでも辻褄は合う。
  5. 局所的に多量のインスタンスを生成する場合(特にループ内など)は、全体のNSAutoreleasePoolの制御に任せるとメモリーをどんどん消費していくので、大量の一時的インスタンスを生成するようなメソッドやループ内ではローカルのNSAutoreleaseaPoolを適用する。

 

コメント

このブログの人気の投稿

リピート

Appling 2ndの『リピート』 の記事を読んで、面白そうだったので買って読んでみた。 リピート posted with amazlet on 07.04.01 乾 くるみ 文藝春秋 (2004/10/23) 売り上げランキング: 194098 Amazon.co.jp で詳細を見る そもそもタイムスリップものは好きなジャンルであることと、そのタイトルと紹介されていたストーリーから大好きなケン・グリムウッドの『リプレイ』を連想したからだ。実際、作品の中でも時間を繰り返すタイムスリップを突きつけられた時主人公達はケン・グリムウッドの『リプレイ』を引き合いに出して議論を行う場面もあるので、作者がリプレイを意識しているのは確実だろう。 タイムスリップ物というSF的な文法を使いながら、うまくミステリーの要素を融合させ、ちょっぴり恋愛物のスパイスも利かせてうまい具合にまとまった作品になっている。たまたま今回は筋が読めてしまって「やっぱり」って感じもしたが、一般的には二つの要素がかみ合ってうまく最後まで読者を惹きつけるだろう。 帯には" 『リプレイ』+『そして誰もいなくなった』 "などと銘打ってある。確かに楽しめる作品であるが、ちょっと言いすぎかな。『リプレイ』のオマージュと考えると、以下の点で物足りなさを感じる。 リプレイ(リピート)の期間が短い 『リプレイ』では25年だったリプレイの期間がわずか10ヶ月を遡るだけ。このことで「人生を繰り返す」といっても重みが違ってきている。しかも描かれるのはそのあるサイクルだけだ。 描写される時代 『リプレイ』では自信を失っていた80年代のアメリカから良き時代の60年代、70年代がリプレイで描かれるところが大きな魅力になっている。一方『リピート』では振幅が短いためリピート自体にそういった効果はないが、何故かそもそもの設定は91年となっていて中途半端に懐かしい。 主人公が嫌なヤツ 最初は好青年っぽいがだんだん嫌な面が描かれていく。物語は一人称で綴られているが主人公がそんな状態であるため、読み手としては感情移入がし難い。 とはいえ、結構な長編だが一日で一気に読んでしまったほど面白い。

PaSoRi

Auの携帯 W41CAではFelica関連のアプリケーションをいくつか使っていたので、nineに移行するとこまるのではと思い、SonyのFelicaのリーダー/ライターであるPaSoRiを購入。 以前は「なんでこんなものお金払ってまで」と思ったものだが、実際にEdyを使うのに慣れてしまうと財布の出し入れなく買い物できたり、小銭をジャラジャラ持って歩かなくて済むメリットは大きい。オフィスにTallysが入っていて毎朝ここでコーヒーを買うのにもEdyを使っているので実感している。 携帯のEdyアプリだと残高やチャージがその場でできるが、カードだとお店に行かないとこういったことができないのはやはり不便だ。 SONY RC-S320 非接触ICカードリーダ/ライタ PaSoRi 「パソリ」 posted with amazlet on 07.05.20 ソニー 売り上げランキング: 15 Amazon.co.jp で詳細を見る Windows専用だがドライバとそれぞれのカードのアプリケーションが附属するので簡単に使用を開始できる。Edyのビューアーはバージョンアップしており最初の起動で最新バージョンを再導入するように促される。使用履歴や残額はその場で確認でいる。チャージはサービス登録をして手続きが完了するのに2日ほどかかるが、これもアプリケーションからオンラインで登録できる。カードは以前作ったANAのマイレージクラブのカードがあったので早速登録。 Macでは正式なサポートはない。 しかし、オープンソースで開発されている LibPaSori を利用したいくつかのアプリケーションがあり、例えば EdyValue データの読み出しだけならば可能となる。

隠された想い

あり得ない…… yostosには、『たまにでいいから相手して』という意味が込められています。 http://shindanmaker.com/69604 引用元: あなたのIDにはあなたの想いが隠されていたー