[ARC対応] Bridgeキャストの使い方

エキスパートObjective-Cプログラミングを読んでの Bridge キャストの学習メモ。

id 型と void* 型 (Objective-C の型と CoreFoundation の型) の相互のキャストは ARC を有効にするとコンパイラによるオーナーシップの管理を自動的に使用できない。 そのため Bridge キャストを利用して明示的にキャストしなければならない。 NS オブジェクトと CF オブジェクトとの構造は同じであるためオーバーヘッド無しにキャストできる。これを Toll-Free Bridge (交通量無料の橋)と呼ぶ。

参照カウントを見るための関数を準備しとく

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

これが何かは ARC 有効時にretain, release, retainCount と同様に参照カウントを操作する を参照

CF オブジェクトには ARC は効かない

ARCObjective-C のオブジェクトに対してのみ有効なので CF オブジェクトにたいしては CFRelease, CFRetain を利用してメモリの管理を行う。CFRelease, CFRetainARC 無効時の NSObject#release, NSObject#retain と同様に参照カウンタを操作して行うとなる。

  CFMutableArrayRef cf1;
  {
    CFMutableArrayRef cf2 = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    cf1 = cf2;
    printf("%ld \n", CFGetRetainCount(cf2)); // 1 と表示される

    CFRetain(cf1);
    printf("%ld \n", CFGetRetainCount(cf2)); // 2 と表示される
  }
  printf("%ld \n", CFGetRetainCount(cf1)); // 2 と表示される

  CFRelease(cf1);
  printf("%ld \n", CFGetRetainCount(cf1)); // 1 と表示される

  CFRelease(cf1); // 参照カウンタが0となりオブジェクトは破棄される

  // オブジェクト破棄後のアクセスはNG

CFオブジェクトに __weak__strong などの修飾子をつけても無視される(Xcodeが警告を出す)

CoreFoundation オブジェクト を Objective-C オブジェクト にキャスト

__bridge, __bridge_retained (CFBridgingRetain 関数), __bridge_transfar (CFBridgingRelease 関数) を利用する

キャスト方法意味参照カウンタ
`__bridge``Objective-C`オブジェクトと `CoreFoundation` オブジェクトの相互変換参照カウンタは変化しない
`__bridge_retained``Objective-C`オブジェクトを`CoreFoundation`オブジェクトにキャストする`CoreFoundation`オブジェクトの参照カウンタを1増やす(`CFRetain`)
`__bridge_transfar``CoreFoundation`オブジェクトを`Objective-C`オブジェクトにキャストする`CoreFoundation`オブジェクトの参照カウンタを1減らす(`CFRelease`)

__bridge キャスト

参照カウンタに影響しない相互変換を行う。__unsafe_unretaind キャストと似たようなもの。

Objective-Cオブジェクト を CoreFoundationオブジェクトに __bridge キャスト

  __strong NSMutableArray *ns = [[NSMutableArray alloc] init]; // 参照カウンタは 1
  CFMutableArrayRef cf = (__bridge CFMutableArrayRef)ns;
  printf("%ld \n", CFGetRetainCount(cf)); // 参照カウンタは 1
  CFMutableArrayRef cf;
  {
    __strong NSMutableArray *ns = [[NSMutableArray alloc] initWithCapacity:16];
    cf = (__bridge CFMutableArrayRef)ns;
    CFRetain(cf); // 保持しておかないとスコープ抜けたあとに困る
  }
  printf("%ld", CFGetRetainCount(cf)); // 参照カウンタは 1
  CFRelease(cf); // リリース

CoreFoundation オブジェクト を Objective-C オブジェクト に __bridge キャスト

  CFMutableArrayRef cf = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
  printf("%ld \n", CFGetRetainCount(cf)); // 参照カウンタは 1

  __weak NSObject *ns = (__bridge NSObject *)cf;
  printf("%ld \n", getRetainCount(ns)); // 参照カウンタは 1

  CFRelease(cf); // 解放
  NSLog(@"%@", ns); // (null)
  CFMutableArrayRef cf01 = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
  printf("%ld \n", CFGetRetainCount(cf01)); // 参照カウンタは 1
  // CoreFoundationオブジェクト生成時の参照カウンタは1
  {
    __strong NSObject *ns = (__bridge NSObject *)cf01;
    printf("%ld \n", getRetainCount(ns)); // 参照カウンタは 2
    // __bridgeキャストは参照カウンタに影響しないが、
    // __strong 修飾子のついたObjective-C変数に渡したため参照カウンタ +1

    CFMutableArrayRef cf02 = (__bridge CFMutableArrayRef)ns;
    printf("%ld \n", CFGetRetainCount(cf02)); // 参照カウンタは 2
    // __bridgeキャストは参照カウンタに影響しない
  }
  printf("%ld \n", CFGetRetainCount(cf01)); // 参照カウンタは 1

  CFMutableArrayRef cf03 = cf01;
  printf("%ld \n", CFGetRetainCount(cf03)); // 参照カウンタは 1

  // CFRelease で解放しなければメモリリーク

__bridge_retaind キャスト, CFBridgingRetain 関数

__bridge_retaind キャストは Objective-C オブジェクトを CoreFoundation オブジェクトにキャストするときに使用し、キャスト後に参照カウンタが1増える。

要するに __bridge キャスト後に CFRetain してるだけ。

  CFMutableArrayRef cf01, cf02;
  {
    __strong NSMutableArray *ns = [[NSMutableArray alloc] initWithCapacity:16];
    cf01 = (__bridge_retained CFMutableArrayRef)ns;
    cf02 = (__bridge_retained CFMutableArrayRef)ns;
  }
  printf("%ld \n", CFGetRetainCount(cf01)); // 参照カウンタは 2
  printf("%ld \n", CFGetRetainCount(cf02)); // 参照カウンタは 2
  CFRelease(cf01); // リリース

  printf("%ld \n", CFGetRetainCount(cf02)); // 参照カウンタは 1
  CFRelease(cf02); // リリース -> オブジェクト破棄

CFBridgingRetain 関数は Objective-C オブジェクトを引数に受け取り、それを __bridge_retaind キャストして CFTypeRef を返すだけなので、__bridge_retaind キャストとやってることは同じ。

  CFMutableArrayRef cf01, cf02, cf03;
  {
    __strong NSMutableArray *ns = [[NSMutableArray alloc] initWithCapacity:16];

    //
    // 以下、やってることは等価
    //

    cf01 = (__bridge_retained CFMutableArrayRef)ns;

    cf02 = (CFMutableArrayRef) CFBridgingRetain(ns);

    cf03 = (__bridge CFMutableArrayRef)ns;
    CFRetain(cf03);
  }
  printf("%ld \n", CFGetRetainCount(cf01)); // 参照カウンタは 3

__bridge_transfar キャスト, CFBridgingRelease 関数

__bridge_transfar キャストは CoreFoundation オブジェクト を Objective-C オブジェクト にキャストするときに使用し、キャスト後に参照カウンタが1減る。

CFBridgingRelease 関数は CoreFoundation オブジェクトを引数に受け取り、それを __bridge_transfar キャストして Objective-Cid 型を返すだけなので、__bridge_transfar キャストとやってることは同じ。

  //
  // 下記の3つ、やってることは等価
  //

  CFMutableArrayRef cf01 = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
  __strong NSMutableArray *ns01 = (__bridge_transfer NSMutableArray *)cf01;
  printf("%ld", getRetainCount(ns01)); // 参照カウンタは 1

  CFMutableArrayRef cf02 = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
  __strong NSMutableArray *ns02 = CFBridgingRelease(cf02);
  printf("%ld", getRetainCount(ns02)); // 参照カウンタは 1

  CFMutableArrayRef cf03 = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
  __strong NSMutableArray *ns03 = (__bridge NSMutableArray *)cf03;
  CFRelease(cf03);
  printf("%ld", getRetainCount(ns03)); // 参照カウンタは 1
Share
関連記事