alloc とか init とか retain とか

いろいろメモリ管理ハマったので。

UIView を例にしてみる。UIView はビュー要素のコンテナみたいなもん。こんな感じで使う。

UIView *unko = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[self addSubview:unko];
[unko removeFromSuperview];
[unko release];

1行目でインスタンス生成して
2行目でそのインスタンスをビューに追加して
3行目でインスタンスをビューから削除して
4行目でメモリ解放を行う。


このとき、unkoをretainしちゃダメっぽい。
ObjC ではオブジェクトは retain カウントっていう値を持っていて、これが 0 になったときにガベコレされるっていう機構を持ってるのだとか。
どうやら UIView は、というか ObjC の基本クラス群は alloc とか init〜 とかすると、retain カウントが1増えるらしい。ということは、alloc やら init やらした時点で retain カウントは +1 なんですな。
このカウントは release してやると -1 されるらしい。4行目にあるやつですな。
つまり、UIView *unko = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)] retain]; とかって書くと、2回 release してやらんと解放されない。

それから、このretainカウントは、addSubview した時点でも +1 される。つまり、追加するコンテナ(今回はself)がretainカウントを増やしているわけですな。なので、逆をいうと removeFromSubview では retain カウントが減るわけ。




じゃあ、これをクラスかなんかで書くとどうなるか。
Hoge.h

@interface Hoge : UIView
{
	UIView *rView;
	UIView *aView;
}
@property (retain) UIView *rView;
@property (assign) UIView *aView;

こっちが定義クラスで

Hoge.m

#import "Hoge.m"

@implementation Hoge

@synthesize rView;
@synthesize aView;

- (id)initWithFrame:(CGRect)frame
{
	self = [super initWithFrame:frame];
	if (self) {
		self.rView = [[UIView alloc] initWithFrame:frame];
		[self addSubview:rView];
		
		self.aView = [[UIView alloc] initWithFrame:frame];
		[self addSubview:aView];
	}
	return self;
}

- (void)dealloc
{
	[aView removeFromSuperview];
	[aView release];
	aView = nil;
	
	[rView removeFromSuperview];
	[rView release];
	rView = nil;
	
	[super dealloc];
}
@end

こっちが実装クラス。

ちなみに、これはメモリリークを起こす。

その前に property ディレクティブについてちょっと説明すると、アクセサメソッドを自動で作ってくれる機能のことらしい。
どういうことかというと、ObjC ではクラスのメンバ変数を作った時には基本的にはプライベートでアクセスできなくて、パブリックなアクセサメソッド作ってアクセスするのが一般的らしい。このアクセサメソッドを自動化したのが @property ディレクティブだそうな。

例えば

@interface Foo : NSObject
{
@private
	int bar;
}
@end

この場合 bar にアクセスするためには
- (void)setBar:(int)inValue;
- (int)getBar;
みたいなのを作ってメソッド経由でアクセスするのだけど、下のように

@interface Foo : NSObject
{
@private
	int bar;
}
@property int bar;
@end

@property ディレクティブをつけてあげて、実装クラス側で @synthesize bar とかってやってあげると、このアクセサメソッドが自動で作られるってわけ。自力で実装した場合には [hoge getBar]; とかって書いていたのが、hoge.bar でアクセスできるようにもなったりする。

それで、このpropertyディレクティブにはもう少し機能があって、(retain)とか(assign)っていうものを付け足せる。

@interface Foo : NSObject
{
@private
	UIView *rView;
	UIView *aView;
}
@property (retain) UIView *rView;
@property (assign) UIView *aView;
@end

これを書くとどうなるかっていうと、assignは基本的には何も無い。こんな感じのアクセサメソッドができるっぽい。

- (void)setAView:(UIView*)inView
{
	aView = inView;
}
- (UIView*)getAView
{
	return aView;
}

retain を書くと、セットされたときに retain を自動的につけてくれるっぽい。

- (void)setRView:(UIView*)inView
{
	rView = [inView retain];
}
- (UIView*)getRView
{
	return rView;
}

ちなみに、このセッターが発動するには、self.hoge というように、ドット演算子でアクセスしなければならないという決まりがある。
self.rView = hogeView;
の場合は上のセッターがコールされ retain されるが、
rView = hogeView;
の場合はセッターがコールされないので、ただの代入になる。

さて、前置きが長くなってしまったが、本題にもどる。
メモリリークを起こしてしまうコードのことだが、retain属性を持ってる rView に注目して欲しい。





そう、alloc〜init があって retain カウントが増えているが、さらに retain 属性があるため、retain カウントが +1 され、合計では +2 になっている。これは assign 属性の aView は alloc〜init の +1 だけである。そのため、dealloc では rView は2回 release しないといけないのだ。というより、alloc〜init で生成する場合は retain しちゃダメですな。この場合は assign 属性にすべきではないかな。


あー、ハマって勉強になった。