viva Cocoa Objective-C 入門 第7章 メモリ管理
ホーム メール
 
目次 < 前ページ 次ページ >

 Objective-Cの学習においてメモリ管理はひとつの壁であると言えます。そこでその対応策としてAppleはObjective-C 2.0においてガベージコレクションと呼ばれる「メモリ管理をプログラマが一切しなくて良い機能」を搭載しました。しかし残念なことにiPhoneアプリケーション開発用のObjective-Cではまだガベージコレクションが使えません。これはガベージコレクションがCPUのパワーをある程度必要とするからで携帯電話に搭載されているCPUには少し荷が重たいからです。しかし今後iPhone用アプリケーションの開発を考えておられる方は多いと思います。そこで本書では第12章までは基本的にガベージコレクションを使わずにiPhoneアプリケーション開発にも使える従来のメモリ管理方法のリファレンスカウンタ方式を使って学習を進めていきたいと思います。

 なおリファレンスカウンタ方式からガベージコレクション方式に変更することは非常に簡単です。コンパイラの設定をガベージコレクションを使用してビルドするように変更するだけです。設定方法は第2章4節のMyBrowserですでに説明しております。

メモリ管理

 この節では最初になぜメモリ管理が必要なのかを説明して続いてリファレンスカウンタ方式を中心に説明していきます。一方、ガベージコレクション方式のメモリ管理方法を採用する設定はとても簡単です。この基礎編ではガベージコレクションを採用する方法だけを説明いたします。ガベージコレクションの詳しい仕組みについては実践編で説明しております。

残念ながら、このサイトでは「実践編」は掲載しておりません。ご了承ください。

メモリ管理

■ メモリリーク

 オブジェクトが登場するまでは変数は大まかにグローバル変数とローカル変数の2種類に分かれていました。そしてグローバル変数はアプリケーションが起動中ずっとメモリを占有し続けます。グローバル変数にとってはそうすることが当然の責務だからです。一方、ローカル変数はメソッド(関数)内で作成されそのメソッドが終了する時に自動的に破棄されます。メソッドは何度も呼び出される可能性がありますがローカル変数はそのたびに作成されそのたびに破棄されます。この状況では特に問題が出ることはなかったわけです。

 ところがObjective-CなどのOOPが登場した時に状況は一変します。OOPで新しく登場したオブジェクトはポインタ経由で取り扱うことを前提として設計されました。ポインタというのがそれほど便利であったのとデータの大型化が理由だと思われます。この場合メソッドが呼び出されてローカル変数が作成される時にオブジェクトのローカル変数はポインタとして作成されます。そしてメソッドが終了する時にローカル変数として作成されたポインタは自動的に破棄されます。しかしポインタにアドレスを代入する形で生成されたオブジェクトはメソッドの外側(スコープ外)に作られています。したがってメソッドが終了しても自動的に破棄されることはありません。さらに最悪なことにそのオブジェクトと連絡を取るための唯一の手段であったポインタはメソッド終了と同時になくなっています。このようにしてそのメソッドが呼び出されて終了するたびに使用されることのない無用なオブジェクトがメモリに残っていきます。この状態をメモリリーク(メモリ漏れ)と呼びます。メモリリークはそのままにしておくとそのアプリケーションが使用できるメモリスペースを占有するだけではなく、やがてシステム(OS)が使用するメモリスペースまで占有しシステムエラーを招くこところまで発展する場合があります。

 アプリケーションが終了するときは、メモリリークとなったオブジェクトも含めて、そのアプリケーションが使用しているメモリはすべて破棄されます。このことは少し安心できる事実ですが、世の中には1年365日24時間稼働し続けているサーバープログラムも多数存在しています。そのようなプログラムにとってメモリリークは重大な問題となります。

■ リファレンスカウンタ方式

 すべてのインスタンスはリテインカウント(retain count)という値を持っています。直訳すれば保持カウントになりますが、日本では「リファレンスカウンタ(reference counter)」「保持カウント」「リテインカウント」など色々な呼び方がされています。これらはすべて同じことを表していますが、ここではリファレンスカウンタという呼び方で統一したいと思います。

 retain countを用いたメモリ管理方式についてAppleは明確な名前を付けていません。リファレンスカウンタおよびリファレンスカウンタ方式という名前は荻原剛志先生がObjective-Cの解説のために命名されたものです。
 基礎編ではこのおそらく最も一般的に普及している名前を使って説明を進めていくことにいたします。

 さてインスタンスがallocやcopyで生成された時にこのリファレンスカウンタの値は1になります。1つのオブジェクトから参照されている、あるいは必要されているという意味になります。

 Controller.mでは initメソッドのなかで

	model = [[Model alloc] init];
 とコーディングしています。変数modelにポインタを代入したModelクラスのインスタンスのリファレンスカウンタはこの時点で1になっています。そして同じくController.mのなかのdeallocメソッドでは
	[model release];
 と記述しています。このreleaseメソッドもNSObjectで定義されているメソッドでリファレンスカウンタの値を1つ減らすという働きをします。この時点で変数modelが指し示しているオブジェクトのリファレンスカウンタの値は0になります。リファレンスカウンタの値が0になったオブジェクトは自分自身のdeallocメソッドを呼び出します。deallocメソッドはそこに記述されている処理を実行してから自分自身を破棄します。そして占有していたメモリが解放されることになります。

 Controller.mのdeallocメソッドでは
	[model release];
	[super dealloc];
 というようにスーパークラスのdeallocメソッドを最後に呼び出しています。Initメソッドのちょうど逆ですね。ここでも[super dalloc]の連鎖はルートクラスにまでおよぶことになります。オブジェクトは自身で設定したインスタンス変数だけではなくルートクラスにいたるまでのすべての親クラスのインスタンス変数もメモリ上に確保しています。当然のことながらこれらも破棄しなければそのオブジェクトを破棄したことにはなりません。deallocメソッドの最後の式は[super dealloc];で終わるということはinitメソッドを[super init]ではじめることと同じ一種の決まりです。ぜひ覚えておいてください。

<▽コメント>図:年輪状のメモリのイメージ図を載せるか? ← 時間がないのでパス<△コメント>

 なお、deallocメソッドは上書きすることはできますがコードの中から直接呼び出してはいけないことになっています。そのオブジェクトのリファレンスカウンタが0になると自動的に呼び出される決まりになっています。

■ 自動解放プール

  リファレンスカウンタ方式にはもうひとつの手法があります。 それは自動解放プール(AutoreleasePool)というオブジェクトに登録するという方法です。多くのオブジェクトのリファレンスカウンタは1のままです。ようするにひとつの目的のためだけに生成される場合がほとんどです。そしてそのようなオブジェクトは生成されると同時にその目的も終わっている場合が多いです。そのようなオブジェクトはこの自動解放プールに登録しておきます。そして自動解放プールは一定期間ごとにreleaseされます。つまりその自動解放プール自身が破棄されることになります。そしてその破棄されるときに自動解放プールに登録されていたすべてのオブジェクトに対してreleaseメソッドが送信されます。その結果この自動解放プールに登録されていたオブジェクトも破棄されメモリが解放されることになります。
 なお自動解放プールを使った場合も使わない場合もreleaseメソッドがおこなうことはレシーバのオブジェクトのリファレンスカウンタの値を1減らすことだけです。例えば3つのオブジェクトから参照されていてリファレンスカウンタの値が3になっているオブジェクトは参照している3つのオブジェクトからreleaseメソッドが送信されなければリファレンスカウンタが0になることはありません。つまり1つでもそのオブジェクトを必要としているオブジェクトがある限りはそのオブジェクトが破棄されることはないということです。

   この自動解放プールへ登録する方法は2通りあります。ひとつはオブジェクトに対してautoreleaseメッセージというを送って自動解放プールに登録するという方法です。もうひとつはオブジェクトの生成と同時に自動的に自動解放プールに登録される一時オブジェクトと呼ばれるオブジェクトを生成する方法です。この一時オブジェクトを生成する方法は「コンビニエンスコンストラクタ」の項であらためて説明します。
 なお自動解放プールの生成と破棄のサイクルは1回のイベントループがはじまる時に生成されそのイベントループが終わるときに破棄されます。イベントループとはユーザからマウスによる指示がないか、キーボードからの入力がないかなどを周回して調べる仕組みです。自動解放プールはその1回の周回のたびに生成され破棄されます。これは人の感覚からすれば充分に早いサイクルですが、コンピュータの処理速度から考えると満足できるサイクルではないかもしれません。そのため自動解放プールはイベントループで自動的に生成される以外にもプログラマが任意の場所に生成し任意の場所で破棄することができます。autoreleaseメソッドで自動解放プールに登録されたオブジェクトや一時オブジェクトとして生成されたオブジェクトは最後に生成された自動解放プール、つまり一番新しい自動解放プールに登録される決まりになっています。また一時オブジェクトではなくallocなどで生成されたオブジェクトを自動解放プールに登録したい場合には

	[[[クラス名 alloc] init] autorelease];
 というネストされたメッセージ式を使うことが推奨されています。なお生成と同時に自動解放プールに登録するのではなく、しばらくそのオブジェクトを使用してから autorelease メッセージを送信して自動解放プールに登録することもできます。Controller.mのinitメソッドでは
	[[Model alloc] init];
 と、自動解放プールには登録していません。これは当然のことです。Modelオブジェクトは MyClipに入力された文字列を保持するオブジェクトです。このオブジェクトがたった1回のイベントループでなくなってしまっては意味がないことになります。

■ retain

 今まではreleaseでリファレンスカウンタを1つ減らすことばかり書いてきましたが、逆にリファレンスカウンタを1つ増やすメソッドがretainです。どのような時にretainが使用されるかをあげるとすれば

 などがあります。戻り値で受け取ったオブジェクトは自動解放プールに登録される決まりになっています。そこでこのオブジェクトを使い続けるにはretainメッセージを送信してリファレンスカウンタを2にしておかなければ自動解放プールによって破棄されてしまうことになります。
 また、あるオブジェクト、たとえばAオブジェクトを複数のオブジェクトで使用(参照)する場合もretainしておかなければなりません。Aオブジェクトを使用しているオブジェクトはAオブジェクトが必要なくなればreleaseする決まりになっています。うっかり自分がそのAオブジェクトをretainすることを忘れていた場合、ほかのオブジェクトがすべてAオブジェクトをreleaseした場合、Aオブジェクトを使おうと思っても、もうそのAオブジェクトは存在していないということになります。さらにそれだけでなく破棄されたオブジェクトへのメッセージ送信はエラーとなりプログラムはクラッシュします。

■ オーナーシップ

 AというオブジェクトがXというオブジェクトを生成して使い始めた場合、このXのリファレンスカウンタは1になっています。そしてAはXのオーナーシップ(ownership 、所有権)を持っていると呼びます。実際にはオーナーシップを表す明確な何かがあるわけでありません。そしてXのオーナーシップを持っているAはXを使わなくなった時に必ずXに対してreleaseメッセージを送らなければならない責任をもちます。このことをオーナーシップの放棄と呼びます。なおオブジェクトにautoreleaseメッセージを送った場合はその時点でオーナーシップを放棄したことになります。

 次にAがXのオーナーシップをまだ放棄していない場合(放棄していればおそらくXはすでに存在していないでしょう)BというオブジェクトがXオブジェジェクトを参照しなければならなくなったとします。この場合BはXに対してretainメッセージを必ず送らなければならないということはありません。しかしBがXオブジェクトを継続的に参照したい場合は当然retainメッセージを送ってXのリファレンスカウンタを2にしておいたほうがほうが良いでしょう。なぜなら、AがXに対してreleaseメッセージを送ってオーナーシップの放棄を行ってもXがなくなってしまわないようにしておくためです。このBがXにretainメッセージを送った時点でBはXのオーナーシップを持ったことになります。そしてAがXに対してreleaseメッセージを送ってオーナーシップを放棄した場合はオーナーシップがAからBに移ったことと同じことになります。このことをオーナーシップの移動と呼びます。なおBはXにretainメッセージを送った時点で(オーナシップを持った時点で)BはXが不必要になった場合にはreleaseメッセージを送る責任も持つことになったことを忘れないでください。

セッターメソッドの記述例

 リファレンスカウンタ方式のメモリ管理ではインスタン変数のセッターメソッドの記述にも気を付けなければなりません。もしインスタン変数がオジェクトであれば(オブジェクトの場合は常にポインタになるのでしたね)セッターメソッドは次のうちどちらかの記述方法にするべきだと言われています。

A案
- (void)setString:(NSString *)aString { [aString retain]; [string release]; string = aString; }
B案
- (void)setString:(NSString *)aString { if (string != aString) { [string release]; string = [aString retain]; }

 A案では引数のaStringをいちどretainしています。これはaStringが一時オブジェクトだからだというわけではありません。引数aStringが一時オブジェクトであるかどうかはメソッド側では分かりません。しかし、すでにstring変数に入っているオブジェクトとaStringが同じである可能性はあります。そこで一度aStringをretainしてからstringをreleaseしています。こうしておかないと、もしaStringとstringが同じオブジェクトを参照していた場合、stringをrelease した時点でaStringもなくなってしまいます。

 B案では最初にif文でインスタン変数と引数が同じオブジェクトであるかどうかチェックしています。

 どちらにしても代入する値(引数)を誤って破棄してしまうことを防ぎ、かつ不必要になる元のインスタン変数に登録されていたオブジェクトの破棄を確実に行うために工夫をこらした実装ですが、いまいち納得できない部分が残る式でもあると思います。A案とB案のどちらが主流であるかというとA案が使われる場合が多いと思います。

 なおMyClipのMdel.mで採用しているセッターメソッドは次のようになっています。
- (void)setString:(NSString *)aString { [string release]; string = aString; }

 引数をretainすることなくいきなり古いインスタン変数の値をreleaseで破棄しています。これは引数aStringが確実にstringインスタン変数のオブジェクトとは別個のものが分かっているからです。また引数aStringのリファレンスカウンタも1であることもはっきりしていますので不用なretainも行っていません。これらのことは引数aStringがcopyメソッドで新しく作成されたオブジェクトでリファレンスカウンタが1であることが分かっていることが前提となってはじめて可能な(安全な)コーディングです。後の章でプロパティというアクセサメソッドを自動合成するObjective-C 2.0の新機能を紹介しますが、その中でもcopyメソッドはほぼデフォルトで使われる感じになっています。このあたりがセッターメソッドの新しい主流となるのではないかと思いますが、詳しくはまた「プロパティ」のところで説明したいと思います。

 リファレンスカウンタの値を取得するには

    [レシーバ retainCount];

というメッセージ式を使います。戻り値はNSUInteger型として戻ってきます。NSUInteger型は64ビットシステムに対応するためにObjective-C 2.0 から採用された新しい整数型です。詳しくは後の章で、また説明いたします。

リファレンスカウンタの定石

 ここまで読まれてきて、「リファレンスカウンタとは難しいものだなぁ」と感じられた方が多いと思います。リファレンスカウンタは確かに難しいです。というかややこしいです。そして実際にミスも犯しやすいところです。C言語の壁がポインタであるとすれば、Objective-Cの壁はこのリファレンスカウンタではないかと思えるほどです。しかし喜ばしいニュースが2つあります。そのひとつはさきほどからも言っているとおりObjective-C 2.0ではガベージコレクションが採用されてメモリ管理についてのコードを一切記述しなくてもすませることもできるようになったことです。そしてもうひとつは、このややこしいリファレンスカウンタを形式化して簡単に覚えてしまおうとする定石がいくつか提案されていることです。ここでは、このような定石の中から、アーロン・ヒレガス氏の「Mac OS X Cocoa プログラミング」の中から「releaseに関する規則」をリファレンスカウンタの定石として引用させてもらいたいと思います。いろいろな定石を読んだなかで、1番シンプルでかつ分かりやすく書かれていると思います。なお、この本は翻訳本であり訳者の村上雅章氏の力による部分も多いと思います。また、この文中で出てくる「保持カウント」とはリファレンスカウントことになります。

アーロン・ヒレガス著 村上雅章訳 Mac OS X Cocoaプログラミング p59
releaseに関する規則

 ※ここでの「カレント自動解放プール」とは最後に作られた自動解放プール、つまり1番新しい自動解放プールのことを指しています。

コンビニエンスコンストラクタ

■ コンビエンスコンストラクタ

 メモリの割当てをして、一定の数値で初期化をして、同時に自動解放プールへの登録をして、オーナーシップの放棄をするオブジェクトを「一時オブジェクト」あるいは「一時的オブジェクト」と呼びます。この一時オブジェクトを作成するためのコードは次のように記述することが推奨されています。

	[[[クラス名 alloc] init(もしくはinit○○○)] autorelease];

 しかし多くのオブジェクトは、この使い切りの一時オブジェクトになるケースが多いため、各クラスでは一時オブジェクトを作成するクラスメソッドが用意されています。このクラスメソッドはそのクラス名で始まることになっています。例えば

	[NSString string○○○];
 という感じになります。これでそのオブジェクトはallocされ、initされ、autoreleaseされたことになる便利なメソッドです。この一時オブジェクトを作成するクラスメソッドのことをObjective-C 1.0では特に呼び方が決まっていませんでしたが、Objective-C 2.0からはコンビニエンスコンストラクタと呼ぶようになりました。

 コンビニエンスはご存知のように「便利な」などの意味になりますが、コンストラクタはコンピュータ用語として、「OOPにおいてオブジェクトを生成して初期化するメソッド、もしくは関数」のことを表します。Objective-Cには、このコンビニエンスコンストラクタを除けば純粋な意味でのコンストラクタはありません。その代わりに

	[クラス名 alloc] init○○○];
を使うことになっているわけです。

 お疲れ様でした。これでObjective-C言語のひとつの山場と言えるリファレンスカウンタ方式のメモリ管理についての説明を終わります。

ガベージコレクションの設定

 この項ではガベージコレクションの設定方法だけを説明いたします。ガベージコレクションの言語的仕様としての説明は実践編をご覧ください。

 「ガベージコレクション」の「ガベージ」とはゴミ(garbage)のことになります。スペルを見れば分かりますが発音的には「ガーベジ」のほうが正しいです。しかし日本ではすでに「ガベージ」という呼び方で慣れ親しまれています。なお、ここでも「実践編」は登場してきません。ご了承ください。

■ ガベージコレクションの設定

 左ペインの「ターゲット」のディスクロージャーを開き「MyClip」を選択して、ツールバーの「情報」をクリックします。

図 ガベージコレクションの設定1

 情報ウィンドウが表示されましたら、次の手順で設定します。

図 ガベージコレクションの設定2

  1. 「ビルド」タブを選択し、右上の検索フィールドに「ガベージコレクション」と入力してエンターキーを押します。
  2. 「設定」欄に「Objective-C ガベージコレクション」という項目が表示されます。
  3. 「値」欄を「非対応」から「必須」に変更します。

 次に、「Debug」だけでなく「Release」も同じ設定にしておきましょう。左上の「構成:」ポップアップメニューから「Release」を選んで、先ほどと同じように「Objective-C ガベージコレクション」を「非対応」から「必須」に変更します。

図 ガベージコレクションの設定3


【Xcode 3.1の場合】

 左ペインの「ターゲット」のディスクロージャーを開き「MyClip」を選択して、ツールバーの「情報」をクリックします。

図 ガベージコレクションの設定1 Xcode 3.1


 情報ウィンドウが表示されましたら、次の手順で設定します。

図 ガベージコレクションの設定2

  1. 「ビルド」タブを選択し、のペインを中ほど過ぎまでスクロールして「Objective-C Garbage Collection」を探します
  2. 「値」欄を「Unsupported」から「Required [-fobjc-gc-only]」に変更します。


 次に、「Debug」だけでなく「Release」も同じ設定にしておきましょう。左上の「構成:」ポップアップメニューから「Release」を選んで、先ほどと同じように「Objective-C ガベージコレクション」を「Unsupported」から「Required」に変更します。

図 ガベージコレクションの設定3


以上でガーベジコレクションと呼ばれるメモリ自動管理システムが機能するようになり、メモリ管理に関するコードを記述する必要がなくなります。今までにコードの中に書かれていた release や retain などのリファレンスカウンタ方式に関する記述は無視されるようになります。



目次 < 前ページ 次ページ >


Copyright 2006 - 2010 viva Cocoa. All Rights Reserved.