[Objective-C] ARCにおける `id*`型, 配列, 動的配列 の扱い方

id* 型

  • 関数の引数に指定する id* 型はデフォルトで __autoreleasing となる。
  • id* 型の変数は明示的にオーナーシップ修飾子をつけないとコンパイルが通らない。ただし配列は例外で __strong がデフォルト。
  • id* 型変数への id* 型変数の代入は ARCの修飾子が揃ってないとNG
  • id* 型変数の初期値は不定

    NSString * __autoreleasing * autoreleasing;
    NSString * __weak * weak;
    NSString * __strong * strong;
    
    NSLog(@"%d, %d, %d",
        autoreleasing, weak, strong); // 値は不定
    
    // 以下、すべての行はコンパイルエラー
    NSError **error;
    strong = weak;
    strong = autoreleasing;
    weak = strong;
    weak = autoreleasing;
    autoreleasing = strong;
    autoreleasing = weak;
    __weak NSString *weakArray[10];
    __strong NSString *array[10];
    
    for( NSInteger index = 0; index < 10; index++ ) {
    weakArray[index] = array[index]; // OK
    }
    // この関数はコンパイルを通る
    void sample(NSError **error,
            NSError * __autoreleasing *autoreleasingError) {
    error = autoreleasingError;
    }

配列とARC

  • デフォルトの修飾子は __strong
  • ゼロフィルされる

    const int size = 10;
    NSString *array[size];
    for ( NSInteger index = 0; index < size; index++ ) {
    if ( array[index] != nil ) {
      NSLog(@"ここは通らない");
    }
    }
    
    // 以下の2行はコンパイルを通らない
    __weak NSString *weak[size] = array;
    __autoreleasing NSString *autoreleasing[size] = array;

動的配列とARC

  • 動的配列と ARC は相性が悪いので NSMutableArray などのクラスを使用するべき
  • 動的配列でも ARC は有効だけど、C言語の関数によるメモリの操作に対しては動作しない
  • malloccalloc で確保したメモリは free しないと解放されない( __strong つけても)

※以下、面倒なのでメモリ確保時のNULLチェックはしていません

  //
  // free ではオブジェクトを解放してくれない例
  //
  const int size = 2;

  NSMutableString * __weak weakArray[size];

  NSMutableString * __strong *array = NULL;
  array = (id __strong *)calloc(size, sizeof(id));

  array[0] = [[NSMutableString alloc] initWithString:@"A"];
  array[1] = [[NSMutableString alloc] initWithString:@"B"];
  weakArray[0] = array[0];
  weakArray[1] = array[1];

  array[0] = nil;

  free(array);

  NSLog(@"%@", weakArray[0]); // (null)
  NSLog(@"%@", weakArray[1]); // B

  //
  // free で ARC は効かないので
  // array[1] に入っていたオブジェクトが
  //メモリリークする
  //
  //
  // malloc で確保した領域をゼロフィルせずに沈む例
  //
  NSMutableString * __strong *array;
  array = (id __strong *)malloc(sizeof(id) * 1024);

  // ほぼ間違いなくクラッシュする
  for (NSInteger index = 0; index < 1024; index++) {
    array[index] = nil;
  }

  //
  // malloc で確保したメモリの初期状態は不定なため
  // array[index] = nil としてしまうと
  // 不定な値がさしているアドレスの
  // 存在しないはずのオブジェクトを
  // ARCによりリリースしてしまう
  // 
  // memset でゼロクリアして使わなければならない
  // ※malloc + memset するくらいなら calloc 使うべき
  //
  //
  // memmoveで横着する例
  //
  const int size = 2;

  NSNumber * __strong *arrayA;
  arrayA = (id __strong *)calloc(size, sizeof(id));
  NSNumber * __strong *arrayB;
  arrayB = (id __strong *)calloc(size, sizeof(id));

  arrayA[0] = [[NSNumber alloc] initWithInteger:123];
  arrayA[1] = [[NSNumber alloc] initWithInteger:456];
  memmove(arrayB, arrayA, size * sizeof(id)); // 警告出るけど無視してOK

  NSLog(@"%d", [arrayB[0] integerValue]); // 123
  NSLog(@"%d", [arrayB[1] integerValue]); // 456

  // __weak に代入
  NSNumber * __weak weak[size];
  weak[0] = arrayA[0];
  weak[1] = arrayB[1];

  // 解放
  arrayA[0] = nil;
  arrayB[1] = nil;

  NSLog(@"%@", weak[0]); // (null)
  NSLog(@"%@", weak[1]); // (null)

  free(arrayA);
  free(arrayB);

  //
  // メモリリークは"発生しない"
  //
Share