itok's Lab

昔の開発ネタを記録として残してます

メモリに関して

はじめに断っておきますと、完璧に理解しているとは言い難いわけでして、この文章はほんとにメモです。(Objective-C初心者の知人に説明するのに使いました)ご了承ください。

(おそらく)一般的な話

CやC++では、オブジェクトは「それを作ったオブジェクトが管理する」のが一般的だと思いますが、Objective-Cではそのreference countの仕組みから「それを使うオブジェクトが管理する」ようになります。これがつまりower shipというやつで。
set/get系のメソッドを扱う(コンテナ的な)オブジェクトAの場合、setされたオブジェクトBに対しては中でcountを1つあげる(あるいはcopyする)処理を行い、setした側のオブジェクトは(もし、そのオブジェクトBに用がなければ)破棄しても構わない、ということになります。(たいていは安全のためautoreleaseにすることが多い)これは、NSArrayやNSDictionaryなども同じで、setされたオブジェクトBは中で保持され、arrayやdic自身が破棄される、あるいは、中の要素をremoveされるときに、内部で自動的にcountが1つ下げられます。そのため、Aの外側からsetしたオブジェクトBを明示的に破棄する必要はありません。

<例>

-(void) setString:(NSString*)str
{
    [m_str autorelease];
    m_str = [str copy];
}
-(NSString*) string
{
    return m_str;
}

set/get系ではなく、createするタイプの場合は、メソッドの中で生成されたオブジェクトは基本的にautoreleaseされてから渡されます。そうすることで、メソッドコール側に必然的にowner shipが渡ります。(ちなみに、一般的にクラスメソッドでのインスタンス生成は中でautoreleaseされている)

<例>

-(Object*) createObject
{
   return [[[Object alloc] init] autorelease];
}

autoreleaseというのが便利なようで厄介ですが、単に明示的に破棄する必要がない、というだけで、いつまでも保持されているわけではない、ことに注意していればさほど問題ないはず。かといって、メソッド内でオブジェクトを生成+autoreleaseしているものを頻繁に呼び出したりすると、メモリリークにもつながるので、明示的に破棄しても問題ない場合は必ずreleaseするようにすればよいかと。
ちなみに、Application起動時に1つの大きなautorelease poolが生成されますが、その中でthreadなどを別途起動した場合は、thread内で明示的にNSAutoreleasePoolを生成する必要があります。

経験則?

あるクラスAのメンバ変数でset/get系のメソッドを扱う(コンテナ系の)インスタンスを保持する場合は、もしそれをmutableで保持できるのであれば、init内でallocし、dealloc内でreleaseするようにする。つまり、そのクラスAの生存期間と同じにしてしまい、適宜remove等のメソッドを用いてコンテナの中身を整理するようにする。こうすることで不要なalloc - releaseを防げますし、double freeなどの問題も事前に回避できるはず。

<例>

-(id) init
{
   if ([super init]) {
       m_array = [[NSMutableArray alloc] init];
       m_dic = [[NSMutableDictionary dictionary] retain];
   }
    return self;
}

(alloc+initでもクラスメソッド+retainでもどちらでもよいが、とにかく明確にowner shipを自分自身に持たせる。メンバ変数に対してautoreleaseされたオブジェクトを渡さない→いつの間にか無くなっていることがあるため)