Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

リファレンスカウンタをもっかい勉強し直す

1年半ぐらい前にiOS4時代でXCode3.2で時が止まっていたので、良い機会なので勉強したメモを書くでぇ。

リファレンスカウンタって何?

iOS5.0以降からみんな大好きARCになったわけだが、リファレンスカウンタ方式の理解が無いのはあかんで。

JavaのGCに慣れてしまうとなかなか面食らいますねぇ。でも理屈は単純なんだよね。オブジェクトがどれだけ他から参照されているかをカウントして0になったらメモリ領域から消えていく。

で、その参照カウンタとして計上されるパターンは大きく2つ。alloc initして自分でオブジェクトを生成するか、retainするかのどっちか。copyした場合は別だよな・・・やべぇ確認しよう。

Foo *foo = [[Foo alloc]init];
Foo *foo2 = [foo retain];

で、仮にFooクラスにhogeっていうプロパティがあった場合、[foo2 release]って書いてもfoo2.hogeはリリースされない。メモリ空間が別物という扱いになるから。deallocを宣言してちゃんと解放していれば別だけど。これに最初困った。releaseしたらnullになるからぬるぽになるんじゃねって思ったけど、どうも違うらしい。そうやって空気を読むのはARCとGCだけっぽい。

ARCが有効になっていない環境では、こういうコードはメモリリークを引き起こす可能性が高い。

//Hoge.h
@interface Hoge : NSObject
@property(nonatomic,retain) NSString *name;
@end

//Hoge.m
#import "Hoge.h"

@implementation Hoge
@synthesize name = _name;
-(id)init {
    if (self == [super init]) {
        return self;
    }
    return nil;
}
@end

で、こんな感じで使うとする。

//main
       Hoge *hoge = [[Hoge alloc]  init];
       hoge.name = @"hoge";
        NSLog(@"%@",hoge.name);
        [hoge release];
        //解放されてないから!
        NSLog(@"%@",hoge.name);

コメントにあるように、hoge.nameは解放されなかった。もちろんHogeのdeallocで[_name release]を追加すれば解放されますが。

また、retainってのはシャローコピーなので誰かがretainしたオブジェクトに変更を加えるとretainして取得したオブジェクトも変更される。

releaseにはautoreleaseってのもある。これってメソッド抜けたぐらいで速攻releaseされるぐらいの感覚でよさげ。そのタイミングでautoreleaseが走ってる可能性が。確かめてないけど感覚的にそんな感じ。

オーナーシップがよくわかるコードは、配列にオブジェクトを入れるコードが一番説明がしやすい。

NSMutableArray *arr = [[NSMutableArray alloc] initWithObjects:[Hoge alloc init],[Foo alloc init],nil];

これだとまずい。HogeとFooのオーナシップが二つ存在することになるから。

ひとつは配列arr。もう1つは、このコードを書いているクラスそのもの。どっかでこのarrをリリースすれば配列の要素に対してはNSMutableArrayがちゃんとreleaseしてくれるけど、このクラスが保有するHogeとFooに対するオーナーシップが解放されないし、さっきも書いたけどこのクラスのインスタンス自体をreleaseしても自分でalloc initしたものはリリースされないのでメモリに残り続ける。

なので、autoreleaseしてやるか明示的にreleaseしてやる。

//autoreleaseする場合
NSMutableArray *arr = [[NSMutableArray alloc] initWithObjects:
[[[Hoge alloc] init] autorelease],[[[Foo alloc] init] autorelease],nil;

//自分でreleaseする場合
Hoge *hoge = [[Hoge alloc] init];
Foo *foo = [[Foo alloc] init];
NSMutableArray *arr = [[NSMutableArray alloc] initWithObjects:hoge,foo,nil];
[hoge release];
[foo release];

これでおk。

ただループの中でallocして何かをする場合はループの中でreleaseしてあげたほうが絶対早い。