2011年9月28日水曜日

Objective-Cのオブジェクト初期化エラーに対処する方法

当たり前だけどオブジェクトの初期化に失敗することもあるわけで、その場合はリソースの解放などエラー処理が必要になる。以前書いたクラスを見直していたら、イニシャライザのエラー処理で怪しいコードがあったので正しい方法を調べてみた。 

AppleのThe Objective-C Programming Languageを見てみると答えがそのまま書かれていて、selfに対してreleaseを呼んでからnilを返すとなっている。releaseによってdeallocが呼ばれるので、self自体とインスタンス変数に割り当てたオブジェクトが解放されることになる。

画像データをNSDataで受け取りUIImageとして保持するクラスがあったとすると、次のような感じになる。

- (id)initWithData:(NSData*)data
{
    self = [super init];
    // superのイニシャライザがnilを返してもreleaseは必要ない
    // 既にsuperで行われているため
    if (self) {
        image = [[UIImage alloc] initWithData:data];
        if (!image) {
            // imageの解放は必要ないけどself自体の解放が必要
            [self release];
            return nil;
        }
    }
    return self;
}

- (void)dealloc
{
    // 初期化に失敗していてもimageはnilなので問題ない
    [image release];
    [super dealloc];
}

deallocは初期化に失敗した状態で呼ばれることもあるため、そのような場合でも正しく動くようにしなければいけない。とは言え、上記のコードのように単純にインスタンス変数をreleaseするだけなら問題はない。

  1. allocはメモリを割り当てた後、すべてのインスタンス変数を0にセットする(0 == nil
  2. インスタンス変数に割り当てるオブジェクトの初期化に失敗しても、そのオブジェクトのイニシャライザはnilを返すため、インスタンス変数はnilにセットされる
  3. nilに対するメソッドの呼び出しは何も起きないことが保証されている

したがって、インスタンス変数はオブジェクトが割り当てられているか、nilがセットされているかのどちらかなので、deallocは正常に実行される。

ちなみに、free(NULL)delete 0も何も起きないことが保証されている。ただし、CFReleaseはリファレンスによると、

If cf is NULL, this will cause a runtime error and your application will crash.

とのことなのでNULLチェックが必要。