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

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を適用する。

 

コメント

このブログの人気の投稿

Google Calaboration

GoogleがCalDAVプロトコルを完全サポートさいた Calaboration を発表した。 まだ、Google Codeの扱いだが一部では大変な騒ぎになっている。 Calaboration を使用すると、設定をするだけでGoogle Calendarのスケジュー ルをiCalにエクスポートでき、そして双方向に同期させることができるという Macユーザーにとっては大変便利な機能を提供してくれるユーティリティ。 騒ぎになっているのは、 Spanning Sync のユーザーフォーラム。 なにせこのソフトはGoogle CalendarとiCalを同期させるというもの。全くバッ ティングしている。多くのユーザーが「Spanning Syncと何が違うんだ? どっ ちがいいんだ? Spanning Synはどうなるだ?」という質問の書き込みを始め た。 Spanning Sync Blogでは、「 How Does Google CalDAV Compare to Spanning Sync? 」という記事が掲載されたが、そこで主張されている違いは以下のような点。 大きな違いはコスト。 Googleは無料だが、Spannning Syncは年間利用料が必要であること。でも、わ たしは永久ライセンスを購入してしまったので関係ない。 iPhoneサポート。 GoogleのCalDAVを使った同期では、iPhone上で予定がread onlyとなって編集 できないとのこと(わたしは設定していないので未確認)。Spanning Syncで はGoogle Calender、iCal、iPhone上すべてで予定を編集できる。 カスタマーサポート。 たしかにGoogleが何かやってくれそうな気はしない。Spanning Syncはお金払っ ているしね。 コンタクトリストの同期。 Googleが提供していない機能として、Spanning SyncはMacのアドレス帳と Gmailのコンタクトを同期してくれる。アドレス帳の画像まで含めて同期して くれるのは驚きだ。もっとも日本語の場合は姓と名が逆転してしまうが、これ は姓名を分けて持っているアドレス帳と姓名を1フィールドで持っている Googleの違いから仕方がないだろう。Googleではないが

Scrivener 日本語チュートリアル

(2022-01-11) 以前からコメントをいただいていましたが、反応できていなく申し訳ありません。 問い合わせが多いので、以下のブログに転載してチュートリアルもダウンロードできるようにしました。 転載先 先日から[試していたScrivenerだが、すっかり気に入り勢い余ってチュートリアルの日本語訳版を作ってしまった。 作者にコンタクトしたところ、「どうぞ、公開しておくれ!」と快く承諾をもらえたので公開しておく。 チュートリアル自体はわたしが慣れないBritish Englishで書かれているため、微妙なニュアンスは違っている箇所があるかもしれあない。また、チュートリアル自体に関係ない言い回しなどは、端折ったり日本語に合うようにゆがめたりしている。誤りがあれば指摘ください。 Scrivener-Tutorial-Japanese.dmg(リンクを切りました。転載先をごらんください。) ちなみに作者のKeithは豪気にもライセンス進呈を申し出てくれたが、入れ違いで購入してしまっていたわたし…… (2010-09-20) 「 design non design 」で紹介いただきました。

EagleFiler is the best organizer software on MacOS X

しばらく放置気味だった Journler をまじめに使おうかと検討していましたが、どうも昨年の7月くらいから開発が止まっている節が感じられます。たくさん文書はリソースを保管した挙げ句に、製品として終わってしまうとかなり悲惨な痛手を被りそうな予感がします。 そこで、以前のバージョンを購入した MacJournal がアップグレード可能なので検討しました。が、 Journler のようにDocumentフォルダでなくデータを~/Library/Application Support/MacJournalに置くのが問題です。これではSpotlightに引っかからないし、バックアップも面倒です。このため、 MacJournal は選択肢から落としました。 いろいろと考えて行くと、 Journler には大きく2つの役割を期待していたようです。 日誌 日々ネットで入手した情報のアーカイブ 前者については別にブログもあるので、問題を棚上げにしました。その上で、後者をサポートできるアプリケーションを探すことにしました。 以前からこの分野では Yojimbo が有名です。 少し触ってみましたが、どうもインターフェイスがしっくり来ません。ソフトのネーミング("Yojimbo"="用心棒")もどうもフィーリングが…… 次に検討したのは、 Together 。 以前はKIT("Keep It Together"の意)と称していたようです。アップデートも頻 繁で勢いを感じます。インターフェイスも一般受けしそうで優れたものだと思 いますが、どうもこちらもネーミングでルー大柴を思い浮かべてしまって…… 結局、C-Commandの EagleFiler というソフトに落ち着きました。 EagleFiler は、F1キーでとにかくブラウザに表示されているものを取り込んでしまえます。標準ではWebArchive形式ですが、スタイルシートなどが外部に依存したままとなるのでわたしはPDF形式で取り込むようにしています。標準でRTF、Plain Text, HTML,PDFなどがサポートされています。また、他の同様のソフトにない機能としてメールがeml形式でそのまま取り込めのは非常に便利な点です。