[Objective-C] ARC有効時に `retain, release, retainCount` と同様に参照カウントを操作する

評判の良さそうなエキスパートObjective-Cプログラミングを読んでARCを学習中…

ARC 有効時にも参照カウントは取得できる

ARC 有効時には retain, release, retainCount が使用できなくなり、 参照カウントを直接増減させたりその値を取得することができなくなった…という訳でもなく取得しようと思えばできる。

CoreFoundation の参照カウントを操作する関数は利用可能なのでこれを利用すればよい。Objective-Cのオブジェクトは CoreFoundation のオブジェクトと相互変換可能なので、CoreFoundation のオブジェクトに変換すれば参照カウントを取得可能となる。ただ、推奨されない方法であり使うべきではない。

ということで以下ような関数を作っておけば参照カウンタを操作できる

NSInteger getRetainCount(__strong id obj) {
  return CFGetRetainCount((__bridge CFTypeRef)obj);
}

void incrementRetainCount(__strong id obj) {
  CFRetain((__bridge CFTypeRef)obj);
}

void decrementRetainCount(__strong id obj) {
  CFRelease((__bridge CFTypeRef)obj);
}

__bridge キャストで Objective-C の型を CoreFoundation の型に変換する。

  • CFGetRetainCountretainCount
  • CFRetainretain
  • CFReleaserelease

と同様の操作を行うことが出来る。

簡単な使用例

{
  __strong NSMutableArray *a = [[NSMutableArray alloc] init];
  NSLog(@"%d", getRetainCount(a)); // 1 と表示される
}

メモリリークする例

  {
    __strong NSMutableArray *obj = [[NSMutableArray alloc] init];
    NSLog(@"%d", getRetainCount(obj)); // 1 と表示される

    incrementRetainCount(obj);
    NSLog(@"%d", getRetainCount(obj)); // 2 と表示される
  }
  //
  // メモリリーク
  //

挙動が怪しいように感じたけどそうでも無かった例

  __weak NSMutableArray *weakObj = nil;
  {
    __strong NSMutableArray *obj = [[NSMutableArray alloc] init];
    weakObj = obj;
    NSLog(@"%d", getRetainCount(obj)); // 1 と表示される

    incrementRetainCount(obj);
    NSLog(@"%d", getRetainCount(obj)); // 2 と表示される
  }
  NSLog(@"%d", getRetainCount(weakObj)); // 1 と表示され…ない。なぜか2、増えてる

  decrementRetainCount(weakObj);
  NSLog(@"%d", getRetainCount(weakObj)); // 0 と表示され…ない。なぜか3、増えてる
  NSLog(@"%d", getRetainCount(weakObj)); // 0 と表示され…ない。なぜか4、増えてる
  NSLog(@"%d", getRetainCount(weakObj)); // 0 と表示され…ない。なぜか5、増えてる

  NSLog(@"%@", weakObj ? @"not nil" : @"nil"); // まだ解放されていない

なぜか参照カウントが減るべきところで増えているのは NSLog などの関数の引数に weak オブジェクトを渡すと retain & autorelease されるのが原因で、不正な挙動をしている訳ではない。 これは参照カウントを操作したこととは無関係なのでメモリリークしたりクラッシュもしない。

Share
関連記事