10

次のコードを実行するとします。

__block int step = 0;

__block dispatch_block_t myBlock;

myBlock = ^{
     if(step == STEPS_COUNT)
     {
         return;
     }

     step++;
     dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2);
     dispatch_after(delay, dispatch_get_current_queue(), myBlock);
};

dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2);
dispatch_after(delay, dispatch_get_current_queue(), myBlock);

ブロックは外部から 1 回呼び出されます。内部呼び出しに達すると、プログラムは詳細なしでクラッシュします。GCD ディスパッチの代わりにどこでも直接呼び出しを使用すると、すべて正常に動作します。

また、ブロックのコピーを使用して dispatch_after を呼び出してみました。これが正しい方向への一歩だったかどうかはわかりませんが、それを機能させるには十分ではありませんでした.

アイデア?

4

5 に答える 5

16

この問題を解決しようとしたときに、再帰的なブロック関連の問題の多くを解決するコードのスニペットを見つけました。ソースを再度見つけることができませんでしたが、コードはまだあります。

// in some imported file
dispatch_block_t RecursiveBlock(void (^block)(dispatch_block_t recurse)) {
    return ^{ block(RecursiveBlock(block)); };
}

// in your method
dispatch_block_t completeTaskWhenSempahoreOpen = RecursiveBlock(^(dispatch_block_t recurse) {
    if ([self isSemaphoreOpen]) {
        [self completeTask];
    } else {
        double delayInSeconds = 0.3;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), recurse);
    }
});

completeTaskWhenSempahoreOpen();

RecursiveBlock非引数ブロックを許可します。単一または複数の引数ブロックに対して書き換えることができます。この構成を使用すると、メモリ管理が簡素化されます。たとえば、保持サイクルの可能性はありません。

于 2013-02-06T13:18:05.417 に答える
5

私のソリューションは完全に Berik のものでした。ここで取り上げる非同期のケースを含め、「再帰ブロック」の問題空間 (他の場所では見つけられなかった) には、より一般的なフレームワークが必要だと感じました。

これらの最初の 3 つの定義を使用すると、4 番目と 5 番目の方法 (単なるに過ぎません) が可能になります。これは、任意のブロックを任意の制限まで再帰する、信じられないほど簡単で、誰にでもできる、(私が信じている) メモリセーフな方法です。

dispatch_block_t RecursiveBlock(void (^block)(dispatch_block_t recurse)) {
    return ^() {
        block(RecursiveBlock(block));
    };
}

void recurse(void(^recursable)(BOOL *stop))
{
    // in your method
    __block BOOL stop = NO;
    RecursiveBlock(^(dispatch_block_t recurse) {
        if ( !stop ) {
            //Work
            recursable(&stop);

            //Repeat
            recurse();
        }
    })();
}

void recurseAfter(void(^recursable)(BOOL *stop, double *delay))
{
    // in your method
    __block BOOL stop = NO;
    __block double delay = 0;
    RecursiveBlock(^(dispatch_block_t recurse) {
        if ( !stop ) {
            //Work
            recursable(&stop, &delay);

            //Repeat
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), recurse);
        }
    })();
}

次の 2 つの例では、再帰メカニズムと対話する機構が非常に軽量であり、基本的にはブロックをラップするrecurse必要があり、そのブロックはBOOL *stop再帰を終了するためにある時点で設定する必要がある変数を受け取る必要があることに注意してください。 (一部の Cocoa ブロック イテレータでおなじみのパターン)。

- (void)recurseTo:(int)max
{
    __block int i = 0;
    void (^recursable)(BOOL *) = ^(BOOL *stop) {
        //Do
        NSLog(@"testing: %d", i);

        //Criteria
        i++;
        if ( i >= max ) {
            *stop = YES;
        }
    };

    recurse(recursable);
}

+ (void)makeSizeGoldenRatio:(UIView *)view
{
    __block CGFloat fibonacci_1_h = 1.f;
    __block CGFloat fibonacci_2_w = 1.f;
    recurse(^(BOOL *stop) {
        //Criteria
        if ( fibonacci_2_w > view.superview.bounds.size.width || fibonacci_1_h > view.superview.bounds.size.height ) {
            //Calculate
            CGFloat goldenRatio = fibonacci_2_w/fibonacci_1_h;

            //Frame
            CGRect newFrame = view.frame;
            newFrame.size.width = fibonacci_1_h;
            newFrame.size.height = goldenRatio*newFrame.size.width;
            view.frame = newFrame;

            //Done
            *stop = YES;

            NSLog(@"Golden Ratio %f -> %@ for view", goldenRatio, NSStringFromCGRect(view.frame));
        } else {
            //Iterate
            CGFloat old_fibonnaci_2 = fibonacci_2_w;
            fibonacci_2_w = fibonacci_2_w + fibonacci_1_h;
            fibonacci_1_h = old_fibonnaci_2;

            NSLog(@"Fibonnaci: %f %f", fibonacci_1_h, fibonacci_2_w);
        }
    });
}

recurseAfterここでは不自然な例を提供しませんが、ほとんど同じように機能します。私はこれら 3 つすべてを問題なく使用しており、古い-performBlock:afterDelay:パターンを置き換えています。

于 2013-09-21T19:10:58.343 に答える
4

遅延変数以外は問題ないようです。ブロックは常に、1 行目で生成された同じ時間を使用します。ブロックのディスパッチを遅らせたい場合は、毎回 dispatch_time を呼び出す必要があります。

    step++;
    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2);
    dispatch_after(delay, dispatch_get_current_queue(), myBlock);
};

編集:

理解します。

ブロックは、ブロック リテラルによってスタックに格納されます。myBlock 変数は、スタック内のブロックのアドレスに置き換えられます。

最初の dispatch_after は、スタック内のアドレスである myBlock 変数からブロックをコピーしました。そして、このアドレスは現時点で有効です。ブロックは現在のスコープにあります。

その後、ブロックはスコープアウトされます。この時点で myBlock 変数に無効なアドレスが含まれています。dispatch_after は、コピーされたブロックをヒープに持っています。安全。

そして、ブロック内の 2 番目の dispatch_after は、スタック内のブロックが既にスコープ アウトされているため、無効なアドレスである myBlock 変数からコピーしようとします。スタック内の破損したブロックを実行します。

したがって、ブロックを Block_copy する必要があります。

myBlock = Block_copy(^{
    ...
});

そして、ブロックが必要なくなったら、ブロックを Block_release することを忘れないでください。

Block_release(myBlock);
于 2011-04-01T07:40:40.927 に答える
-1

ブロックを残しておきたい場合は、ブロックをコピーする必要があると思います(ブロック自体を呼び出したくない場合は解放します)。

于 2011-03-22T16:56:28.053 に答える