2

GCDを使用したObjective-Cで次の問題が発生しました。理解できません。

次の方法を使用して、648個のタイルの計算を行っています。最初に処理するタイルの順序は、変数「pi」を設定するアルゴリズムによって指定されます。変数「loaded」はこのコンテキストではグローバルであり、0から始まり、正しく647まで上がります。

ブロックを使用しない場合、すべてが完全に機能します。

while (loaded < [self.tiles count]) {
    long pi = /* tricky way to calculate the position index to set */;
    NSLog(@"loaded: %d", loaded);
    // Do this in a separate thread
    dispatch_async(loader, ^{
        NSLog(@"loaded ->: %d", loaded);
        [self.tiles[loaded] setPositionIndexTo:pi];
    });

    loaded++;
}

問題:

ブロックがself.tiles[648]にアクセスしようとするため、例外が発生します。Appleのブロックドキュメントでは、ブロックが作成されたときにブロック内の変数の値がキャプチャされると述べているので、これがどのように可能であるかさえわかりません。実際にロードされた変数の値は648であり、実行せずにループを中止する必要があることを理解しています。他の奇妙な点は、値0もブロックで使用されないことですが、1から始まります。また、ロードされた値に関してブロックがループの前にあることを確認できる場合もあり、値をまったく見逃している場合もあります。 、または2回実行します。ここにいくつかの出力があります:

 loaded ->: 612
 loaded: 613
 loaded ->: 613
 loaded ->: 614
 loaded: 614
 loaded ->: 614
 loaded: 615
 loaded: 616
 loaded ->: 615
 loaded: 617
 loaded ->: 617

なぜ、そしてどのようにこれが可能ですか?

Appleのドキュメントには、ブロックが作成されたときに、ブロックに対して変更せずに「ロード済み」の値をキャプチャする必要があることが明確に記載されていると思いますが、ご説明いただきありがとうございます。

4

2 に答える 2

7

問題は、それloadedがローカル変数ではなくインスタンス変数であることです。ブロックはローカルスコープをキャプチャします。Objective-C では、インスタンス変数にアクセスすると、コンパイラはそのアクセスを構造体メンバー アクセスに変換します。

@interface MyClass : NSObject
{
    int varA;
}
@end

@implementation MyClass
- (void)someMethod
{
    varA = 42; // This
    self->varA = 42 // is actually this after compilation
}
@end

そのため、ブロックは実際に を参照しているため、実行時に常に load の現在の値が表示されself->loadedます。

この問題には複数の解決策がありますが、一般的には、各ブロックが に対して一意の値を認識していることを確認する必要がありますloaded。これを行う簡単な方法はloaded、ローカル変数を作成することです。全体的な進行状況を追跡する必要がある場合は、ブロックから進行状況プロパティを更新します。(loaderが同時キューである場合、進行状況プロパティを更新するときにマルチスレッドの問題に注意する必要があることに注意してください。簡単な解決策は、更新をカスタム シリアル キューにディスパッチすることです。)

于 2013-02-27T18:28:44.520 に答える
1

囲んでいる字句スコープにローカルなすべてのスタック (非静的) 変数は、const 変数としてキャプチャされます。あなたの場合のようなインスタンス変数は、通常の変数としてアクセスされます。

問題を解決するには、値がロードされたローカル変数を取得し、それをブロック処理に使用します。

int index = loaded;

while (index < [self.tiles count]) {
    long pi = /* tricky way to calculate the position index to set */;
    NSLog(@"loaded: %d", index);
    // Do this in a separate thread
    dispatch_async(loader, ^{
        NSLog(@"loaded ->: %d", index);
        [self.tiles[index] setPositionIndexTo:pi];
    });

    index++;
}
于 2013-02-27T18:35:27.300 に答える