异步调度递归块

时间:2011-03-22 16:35:15

标签: iphone objective-c grand-central-dispatch objective-c-blocks

假设我运行此代码:

__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);

从外部调用一次该块。到达内部调用时,程序崩溃,没有任何细节。如果我在任何地方使用直接调用而不是GCD调度,一切正常。

我也尝试使用块的副本调用dispatch_after。我不知道这是否是朝着正确方向迈出的一步,但这还不足以让它发挥作用。

想法?

5 个答案:

答案 0 :(得分: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允许非参数块。可以为单个或多个参数块重写它。使用此构造简化了内存管理,例如,没有保留周期的可能性。

答案 1 :(得分:5)

我的解决方案完全来自Berik's,所以他在这里获得了所有的荣誉。我只觉得“递归块”问题空间(我在其他地方没有找到)需要一个更通用的框架,包括异步情况,这里介绍了。

使用这三个第一个定义使得第四个和第五个方法 - 只是示例 - 成为可能,这是一种非常简单,万无一失的,并且(我相信)内存安全的方法来递归任何块任意限制。

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);
        }
    })();
}

您将注意到,在以下两个示例中,与递归机制交互的机制非常轻量级,基本上相当于必须在recurse中包含一个块,并且该块必须采用{{1}变量,应该在某个时候设置退出递归(在一些Cocoa块迭代器中是熟悉的模式)。

BOOL *stop

- (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模式。

答案 2 :(得分: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在堆中具有复制的块。这很安全。

然后,块中的第二个dispatch_after尝试从myBlock变量复制,该变量是无效地址,因为堆栈中的块已经确定了范围。它将在堆栈中执行损坏的块。

因此,您必须阻止Block_copy。

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

当你不再需要它时,不要忘记Block_release块。

Block_release(myBlock);

答案 3 :(得分:0)

选择自定义调度源。

dispatch_queue_t queue = dispatch_queue_create( NULL, DISPATCH_QUEUE_SERIAL );
__block unsigned long steps = 0;
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
dispatch_source_set_event_handler(source, ^{

    if( steps == STEPS_COUNT ) {
        dispatch_source_cancel(source);
        return;
    }

    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2);
    dispatch_after(delay, queue, ^{
        steps += dispatch_source_get_data(source);
        dispatch_source_merge_data(source, 1);
    });

});

dispatch_resume( source );
dispatch_source_merge_data(source, 1);

答案 4 :(得分:-1)

认为你必须复制该块,如果你想要它一直存在(当你不想让它自己调用时释放它)。