Objective-C メモリ解放,retainCount,release alloc 【初級編 第13回】

この記事は2012年11月18日に書かれたものです。内容が古い可能性がありますのでご注意ください。


Objective-C メモリ解放,retainCount,release alloc 【初級編 第13回】

前回Objective-Cでのイニシャライザに関して述べました。
allocを使用してメモリ上に確保したオブジェクトの領域をイニシャライザを使用して初期化するという流れでしたね。
このとき確保したメモリはreleaseを使用して解放するといいました。
今回はその解放に関して説明したいと思います。

変数を宣言するとメモリ上に指定された型のサイズの分だけ領域が確保され、
変数名によりその領域を区別するというのは以前に述べました。
このとき、格納されているのがデータそのものの場合は通常のデータ型の変数となり、
データを格納した領域のアドレスが格納されているのがポインタ型の変数でした。
そしてObjective-Cではインスタンス化したクラスにアクセスするときはポインタ型にしなければならない
というのがその10で説明したところとなります。

いずれの方法にしろ確保したメモリの領域は返さなければなりません。
データ型の変数やポインタ型の変数で確保されたメモリ領域がいつプログラムから解放されるのかを見てみましょう。

あるメソッド内部で一時的に値を保持するために宣言された変数はそのメソッドの中でのみ有効です。
こういう変数をローカル変数といいます。
例えば「main.m」でPersonクラスをインスタンス化した際にmineという変数にアドレスを格納しています。
このmineはmainメソッド内でのみ有効です。

このローカル変数はメソッドなどが実行される際に読み込まれるスタック領域というメモリ領域に確保されます。
このスタック領域に確保されたものは自動的に解放されます。
つまりメソッドの処理が終了すると自動的にメモリ上の確保されている領域が解放されるということになります。
データ型(intとかdobuleとか)の変数は、その値の格納場所がスタック領域なのでメソッドが終了し、
その領域が解放されると中身も解放されます。

しかし、クラスをインスタンス化した場合はポインタ型となります。
インスタンス化により確保される領域は先ほどのスタック領域ではなく、ヒープ領域と呼ばれる部分になります。
ポインタ型の変数はこの確保したヒープ領域のアドレスを格納するものですが、
アドレスを格納している変数自体はスタック領域に確保されます。
先ほど言ったようにスタック領域はメソッドの処理が終わると自動で解放されますが、ヒープ領域は解放されません。
ということはポインタ型の場合、アドレスの入っている変数が破棄されるだけで、
確保されたヒープ領域のメモリは残ったままとなり、そこにアクセスする手段がなくなるということになります。
プログラムが終了すればこの確保された領域も解放されますが、それまでは確保されたままです。

こういったことが頻繁に行われるとメモリ領域が圧迫されることになりますし、
長時間動かしたままにするプログラムではなかなか解放されないといったことがおこります。
使っていないメモリが解放されずに領域を消費し続けることをメモリリークといいます。
このメモリリークを起こさないためにallocで確保した領域は「release」しなければならないということになります。

ではreleaseすればインスタンス化した際に確保したメモリ領域が解放されるのかというと違います。
Objective-Cではこのメモリ領域をretainCountというもので管理しています。
このretainCountというものを見て、そのオブジェクトの領域を解放するかどうかを判断しています。
この値は確保したメモリ領域(つまりオブジェクト)を必要としているものがどれだけあるかということを表しています。
このカウントが0になると必要としているものがないとして解放されます。

メモリを確保するためのallocはこのカウンタを1にしています。
今のところallocしか出てきていませんが、これ以外にもnewやcopyなどのメソッドでオブジェクトを作成することができ、
これらのメソッドで作成したオブジェクトはretainCountが1となります。
つまり作成したオブジェクトに関しては自身が所有者になるということです。
現在のretainCountを確認する場合は以下のようにします。
main.mのmine変数を見てみます。

main.m

#import <Foundation/Foundation.h>
#import "Bmi.h"
#import "Person.h"

int main(void){
    Person *mine = [[Person alloc] initWithWeight:65.0 height:1.71] ;
    printf("%2.1fn",[Bmi calcBmi:[mine weight] height:[mine height]]);
    printf("%2.1fn",[Bmi calcProperWeight:[mine height]]);
    printf("%dn",[mine retainCount]);
    [mine release];

    return 0;
}

付け加わったのは9行目ですね。Personクラスから作成したオブジェクトにアクセスするための変数mineに
retainCountというメッセージを送信しています。このメッセージにより現在のカウントがみれます。
下の行でreleaseしているのですが、その次の行に再度retainCountのメッセージを送信すると
「セグメンテーション違反です」と表示されます。確保してないメモリ領域にアクセスしようとするとでます。
ここではすでにメモリが解放されているからですね。



3行目がretainCountです。1になってますね。

これら所有するオブジェクトが不要になったらreleaseを使用して所有権を放棄するという流れになります。
このreleaseが呼び出されると0にされるというわけではなく、retainCountを-1します。
元々自身が作成したもので、他のオブジェクトから参照されていなければretainCountが1になっているため、
releaseによって0となり、メモリ解放となります。
メモリ解放時(retainCountが0になったとき)にはdeallocというメソッドが実際に呼ばれます。
このメソッドは直接呼び出してはいけないことになっています。
所有者がいるかもしれないからです。

また自分が所有者でないものはreleaseしてはいけませんし、0になったものにreleaseメッセージを
送信してもいけません。

今回のまとめとしては

  • オブジェクトを作成して確保したメモリ領域は通常は自動で解放されないので自身で解放する必要がある
  • Objective-Cでメモリを管理する方法としてretainCountというものがある
  • allocなどのメソッドでオブジェクトを作成した場合、retainCountが1になる
  • releaseはretainCountを-1するためのものである
  • retainCountが0になるとメモリが解放される

といったところでしょうか。

  • このエントリーをはてなブックマークに追加

PAGE TOP