2013年8月26日月曜日

Objective-Cの勉強(7):オブジェクトの所有権



オブジェクトの所有権


Objective-Cでは、前述の通りクラスはローカルやグローバルな領域に実体を確保できないため、必ずオブジェクトの生成と破棄を必要とする。
アクセスしたメソッドが、それを解放して良いか、解放するならそれはいつかは、わかりにくいこともある。
特にメソッドのリターン値になる場合などはそうである。
誰が解放するかということについて、一応の規約が存在する。


オブジェクトを作成する alloc / allocWithZone:
オブジェクトをコピーする  copy / copyWithZone: / mutableCopy / mutableCopyWithZone: 
オブジェクトを保持する retain
以上を自身で行った場合はreleaseでオブジェクトを解放する。
行ってない場合は「そのオブジェクトの所有権はない」として解放処理は行わない。


別のまとめ方をすると、以下の通りとなる。
ローカルでallocしたそのスコープ内でrelease
クラスメソッド(+で始まるメソッド)で確保された基本的には何もしない(OSが解放する)
または明示的にAutorelease Poolを確保~解放する
プロパティでcopyまたはretaindeallocメソッド内でrelease
allocとcopyは実メモリの確保を伴い、retainはアドレスが示す内容の解放を遅らせるだけ、と意味が異なる。にもかかわらず、同じreleaseで解放指示を行うところが注意である。



allocとcopyは特にわかりにくいことはないと思うが、retainは少しわかりにくい概念かもしれない。
Objective-Cのクラスはアドレスでやりとりされる。
従って、代入したもしくはメッセージに引数として渡したと思って元のオブジェクトを解放してしまうと、代入先が参照できなくなってしまう。これを渡された側で「解放に待ったをかける」方法が「retain」である。
Objective-Cではマルチタスクを前提としているので、呼び出されたメソッドと呼び出したメソッドは並行動作する可能性が高い。そのためこのような指示が必要なのである。
呼び出し側でその後の処理でreleaseをかけても、そこでは解放せず、呼び出された側もreleaseをした時に初めて実解放がかかるようにする。



しかしこれもあくまで解放の遅延だけであって、内容の書き換え禁止ではないことには注意すべきである。元の内容を書き換えると、代入先の内容まで書き換わってしまう。これが顕著になるのが配列オブジェクトを使う場合である。



Objective-CのクラスはCの構造体と見た目似ているが決定的に違う部分があって、それは「実体を代入することが出来ない」である。このことは配列クラスであるNS(Mutable)Arrayの要素にて特に問題になる。
NSArrayの要素はid型であるが、これはすなわち要素として実際に格納されるのはアドレスであることを示している。addObject:メッセージで要素を追加しても、その要素のオブジェクトのアドレスが格納されるだけであって、要素実体のコピーが格納されるわけではい。Cで構造体の配列を作った場合は、その実体がコピーされる。これが最大の違いである。
 
// 構造体の場合
typedef struct {
    int x;
    int y;
} XY;
XY xy[10]; // 構造体XYの実内容を持つワークが10個分用意される。
~
    xy[0]=xy[1]; // xy[1]の内容全てがxy[0]の領域にコピーされる


// クラスの場合
@interface XY : NSObject
{
    int x;
    int y;
}
~
{
    XY *xy0=[[XY alloc]init];
    NSMutableArray *ary=[[NSMurableArray alloc]init];
    //
    [ary addObject:xy0]; // ここではxyのアドレスだけが格納され、xyの実体は格納されない
    XY *xy1=[ary objectAtIndex:0];

(構造体とクラスのプログラムは同じ意味ではない。)
ここで、構造体の場合はxy[1]の内容をこの後いじってもxy[0]の内容は変化しない。
実内容がコピーされているからである。
ところが、クラスの場合、xy0の内容をいじるとxy1から読み出す内容も同じになる。
配列に格納されているのはあくまで実体アドレスであって、内容ではないからだ。
このことを理解しておかないと、「配列に要素を保存したはずなのに内容が変わってる!」という現象に悩まされる。クラスではこれはバグではなく仕様なのである。
仮にretainを指定しても、それはあくまでアドレスの示す領域の解放を遅らせるだけであって、内容の書き換えを禁止するわけではない(その方法はない)ので要注意である。


MacのCocoaではメモリの有効活用のためにワークを移動させてまとめたり不要になった領域をメモリ上から消すガベージコレクション機能を持つらしい。古くはBASICで文字列を扱うと発生していたのが「ガベージコレクション」だし、X68のSX-Window上でもワークの移動はOSの大事な機能だったが、それとほぼ同じである(こんな事書いても解る人の方が少ないだろうが)。
確かにそれがあれば解放を気にしなくて済むようになるからプログラムは楽に書けるようになるかもしれない。
が、本来メモリ管理は明確かつ厳密に行われるべきであって、ガベージコレクションなどに頼るべきではないと思う。
アセンブラやCの時代からプログラムを組んでいると、ガベージコレクションどころか、クラスインスタンス以外のAutorelease Poolだって甘やかしだと思うのだが。

0 件のコメント:

コメントを投稿