5

Objective-C ブロックを使用するコードを書きましたが、結果に混乱しました。

@interface MyTest : NSObject

@end

@implementation MyTest

- (void)test {
    NSArray *array = [self array1];  // ok
//    NSArray *array = [self array2];// crash
//    NSArray *array = [self array3];// ok again

    dispatch_block_t block0 = (dispatch_block_t)[array objectAtIndex:0];
    block0();

    dispatch_block_t block1 = (dispatch_block_t)[array objectAtIndex:1];
    block1();
}

- (NSArray *)array1 {
    int a = 10;
    NSMutableArray *array = [NSMutableArray array];
    [array addObject:^{
        NSLog(@"block0: a is %d", a);
    }];
    [array addObject:^{
        NSLog(@"block1: a is %d", a);
    }];
    return array;
}

- (NSArray *)array2 {
    int a = 10;

    return [NSArray arrayWithObjects:^{
        NSLog(@"block0: a is %d", a);
    }, ^{
        NSLog(@"block1: a is %d", a);
    }, nil];
}

- (NSArray *)array3 {
    int a = 10;

    return [NSArray arrayWithObjects:^{
        NSLog(@"block0: a is %d", a);
    },[^{
        NSLog(@"block0: a is %d", a);
    } copy], nil];
}
@end

私は混乱しています:

  1. なぜarray2がクラッシュするのですか? array1 と array2 の本当の違いは何ですか?
  2. ブロックコピーはブロックをスタックからヒープに移動するという記事を読みましたが、メソッドarray1ではコピーしませんが、それでも機能します。array3 では、2 番目のブロックをコピーするだけで OK になります。なぜ ?
  3. ブロックを使用する場合、どこでコピーを使用する必要がありますか?

ところで、ARCの下でXcode 4.6でコードを実行します。ありがとう

4

3 に答える 3

4

コンパイラが処理しないブロックに関連して型損失のケースを見つけたようです。しかし、最初から始める必要があります...

以下は、ARC でのブロックの使用に関連しています。その他のシナリオ (MRC、GC) は考慮されません。

一部のブロックがヒープではなくスタック上に作成されることは、プログラマが意識する必要がないように技術的に実装できる最適化です。ただし、ブロックが最初に導入されたとき、最適化はユーザーに対して透過的ではないという決定が下されたため、 が導入されました。その時以来、仕様とコンパイラの両方が進化し(そしてコンパイラは実際に仕様を超えています)、(仕様によって)必要ではありませ(コンパイラが仕様を超える可能性があるため)他の人に必要とされます。blockCopy()blockCopy()

最適化を透過的に実装するにはどうすればよいですか?

検討:

  1. コンパイラは、スタックに割り当てられたブロックをいつ作成するかを認識しています。
  2. コンパイラは、そのようなブロックをいつ別の変数に割り当てるかを認識しています。それで
  3. コンパイラは割り当てごとに、ブロックをヒープに移動する必要があるかどうかを判断できますか?

些細な答えは「はい」です。どの割り当てでもヒープに移動します。しかし、それは最適化の目的全体を無効にします-スタックブロックを作成し、それを別のメソッドに渡します。これには、パラメーターへの割り当てが含まれます...

簡単な答えは「試してはいけない」です - 導入blockCopy()してプログラマーに理解させてください。

より良い答えは「はい」ですが、賢くやってください。疑似コードでは、ケースは次のとおりです。

// stack allocated block in "a", consider assignment "b = a"
if ( b has a longer lifetime than a )
{
   // case 1: assigning "up" the stack, to a global, into the heap
   // a will die before b so we need to copy
   b = heap copy of a;
}
else
{
   if (b has a block type)
   {
      // case 2: assigning "down" the stack - the raison d'être for this optimisation
      // b has shorter life (nested) lifetime and is explicitly typed as a block so
      // can accept a stack allocated block (which will in turn be handled by this
      // algorithm when it is used)
      b = a;
   }
   else
   {
      // case 3: type loss - e.g. b has type id
      // as the fact that the value is a block is being lost (in a static sense)
      // the block must be moved to the heap
      b = heap copy of a;
   }
}

ブロックの導入時に、ケース 1 と 3 では手動で を挿入する必要がありblockCopy()、ケース 2 では最適化が功を奏しました。

ただし、以前の回答で説明したように仕様はケース 1 をカバーするようになりましたが、コンパイラはケース 3 をカバーしているように見えましたが、それがわかっていることを確認するドキュメントはありませんでした。

(ところで、そのリンクをたどると、このトピックに関する古い質問へのリンクが含まれていることがわかります。そこに記載されているケースは現在、自動的に処理されます。これは上記のケース 1 の例です。)

ふう、それをすべて手に入れましたか?質問の例に戻りましょう。

  • array1array3およびarray4型損失があるケース 3 のすべての例です。これらは、前の質問でテストしたシナリオでもあり、現在のコンパイラで処理できることがわかっています。それらが機能するのは偶然でも運でもありません。コンパイラは必要なブロック コピーを明示的に挿入します。ただし、これが公式にどこにも文書化されているかはわかりません。
  • array2型損失があるケース 3 の例でもありますが、これは前の質問でテストされていないバリエーションです。可変引数リストの一部として渡すことによる型損失です。このケースは、現在のコンパイラでは処理されていないようです。これで、ケース 3 の処理が文書化されていない理由の手がかりが得られました。処理は完了していません。

前述のように、コンパイラの動作をテストすることは可能です。コードにいくつかの簡単なテストを組み込んで、テストが失敗した場合にアプリケーションをすぐに中止することもできます。したがって、必要に応じて、コンパイラが現在自動的に処理することを知っているものに基づいてコードを書くことができます(これまでのところ、可変引数関数を受け入れると見なされているものはすべて)。

これが役に立ち、理にかなっていることを願っています!

于 2013-06-08T20:12:59.523 に答える
1

これら 3 つすべてがクラッシュします (ただしcopy、 の最初の要素にa がないのarray3はおそらく見落としだと思います)。ブロックが作成されたスコープを超えて存続させたい場合は、ブロックをコピーする必要があります。メソッドが渡したオブジェクトをコピーすることを特に知っていない限り、自分でコピーする必要があります。

于 2013-06-08T17:21:23.040 に答える
0

私もうまく動作する4番目のケースを試しました:

- (NSArray *)array4 {
    int a = 10;

    return @[ ^{
        NSLog(@"block0: a is %d", a);
    }, ^{
        NSLog(@"block1: a is %d", a);
    }
             ];
}

もちろん、これは次と同じです。

- (NSArray *)array4 {
    int a = 10;

    id blocks[] = { ^{
        NSLog(@"block0: a is %d", a);
    }, ^{
        NSLog(@"block1: a is %d", a);
    }
    };
    NSUInteger count = sizeof(blocks) / sizeof(id);

    return [NSArray arrayWithObjects:blocks count:count];
}

したがって、唯一の問題は「array2」にあります。その実装の重要なポイントarrayWithObject:は、可変数の引数をとるメソッドを呼び出すことです。

最初の (名前付きの) 引数だけが適切にコピーされているようです。可変引数はコピーされません。3 番目のブロックを追加すると、2 番目のブロックでも問題が発生します。最初のブロックのみがコピーされます。

したがって、可変引数コンストラクターでブロックを使用すると、最初の名前付き引数のみが実際にコピーされるようです。可変引数はコピーされません。

配列を作成する他のすべての方法では、各ブロックがコピーされます。

ところで-ARCを使用してLion(10.7.5)の下で単純なOS Xアプリを使用して、Xcode 4.6.2を使用してコードと追加を実行しました。iOS 6.1 アプリで同じコードを使用すると、同じ結果が得られます。

于 2013-06-08T17:33:34.420 に答える