2

私はブロックで遊んでいて、奇妙な振る舞いに遭遇しました。これはインターフェース/実装であり、それを実行する機能を備えたブロックを保持するだけです。

@interface TestClass : NSObject {
#if NS_BLOCKS_AVAILABLE
    void (^blk)(void);
#endif
}
- (id)initWithBlock:(void (^)(void))block;
- (void)exec;
@end

@implementation TestClass
#if NS_BLOCKS_AVAILABLE
- (id)initWithBlock:(void (^)(void))block {
    if ((self = [super init])) {
        blk = Block_copy(block);
    }
    return self;
}
- (void)exec {
    if (blk) blk();
}
- (void)dealloc {
    Block_release(blk);
    [super dealloc];
}
#endif
@end

通常のインスタンス化と通常のブロックの受け渡しは機能しますが、次のようになります。

TestClass *test = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass");
}];
[test exec];
[test release];

作成中のオブジェクトを参照してブロックを使用しても、次のことはできません。

TestClass *test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];

エラーはEXC_BAD_ACCESS、Block_copy(block)のスタックトレースです。デバッガー:0x000023b2 <+0050> $ 0x18、%espを追加

私は遊んで、割り当てコードを初期化の上に移動しました、それはうまくいきました:

TestClass *test2 = [TestClass alloc];
test2 = [test2 initWithBlock:^{
    NSLog(@"TestClass %@", test2);
}];
[test2 exec];
[test2 release];

また、両方のスニペットを組み合わせても機能します。

TestClass *test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];

TestClass *test2 = [TestClass alloc];
test2 = [test2 initWithBlock:^{
    NSLog(@"TestClass %@", test2);
}];
[test2 exec];
[test2 release];

何が起きてる?

4

2 に答える 2

5

代入式では、左辺値に代入される前に右辺値が評価されます。

これは、次のことを意味します。

TestClass *test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];

次の一連の操作が実行されます。編集: Jonathan Grynspan が指摘したように、ステップ 1 と 2 の順序が定義されていないため、ステップ 2 がステップ 1 の前に実行される可能性があります。

  1. +allocに送信TestClass
  2. test1まだ初期化されていないを参照するブロックを作成します。test1任意のメモリアドレスを含みます。
  3. -initWithBlock:手順 1 で作成したオブジェクトに送信します。
  4. に右辺値を割り当てtest1ます。

test1は、手順 4 の後でのみ有効なオブジェクトを指すことに注意してください。

の:

TestClass *test2 = [TestClass alloc];
test2 = [test2 initWithBlock:^{
    NSLog(@"TestClass %@", test2);
}];
[test2 exec];
[test2 release];

シーケンスは次のとおりです。

  1. +allocに送信TestClass
  2. 右辺値を に割り当てます。これは、オブジェクトtest2を指すようになりました。TestClass
  3. を参照するブロックを作成します。これは、手順 2 のオブジェクトtest2を指します。TestClass
  4. 手順 2 で正しく割り当てられた に送信-initWithBlock:します。test2
  5. に右辺値を割り当てtest2ます。
于 2011-05-18T23:13:08.513 に答える
1

__block問題は、ブロックが作成されると、キャプチャした非変数がコピーされる (別のコピーが作成される)ことです。ブロックが作成された時点で は初期化されていないため、ブロックを実行するときにtest1初期化されていないポインターを使用することになります。test1

適切な解決策は、で宣言することtest1です__block。こうすることで、ブロックと外側のスコープの間で状態が共有test1され、外側のスコープで が割り当てられた後、ブロックは変更された値にアクセスできます。

__block TestClass *test1;
test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];

ps 3 番目の例 ( のalloc前に実行してから結果を代入する) は、一般に、オブジェクトのメソッドが呼び出されたオブジェクトを返すことが保証されていないinitため、信頼性がありません (例えば、それ自体を解放し、失敗した場合に nil を返すことが許可されています)。 .initinit


更新:__block変数はブロックによって保持されないため、上記のコードは MRC 専用です。

ただし、ARC では、__blockオブジェクト ポインター変数はデフォルトでブロックによって保持されるため、上記のコードは保持サイクルを引き起こします。ARC では、正しいコードは次のとおりです。

TestClass *test1;
__block __weak TestClass *weakTest1;
weakTest1 = test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", weakTest1);
}];
[test1 exec];
于 2011-09-16T22:55:43.737 に答える