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

该块从外部调用一次。当到达内部调用时,程序崩溃,没有任何细节。如果我在任何地方都使用直接调用而不是 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 的,所以他在这里得到了所有的赞誉。我只是觉得“递归块”问题空间(我在其他地方没有找到)需要一个更通用的框架,包括这里介绍的异步案例。

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

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并且该块必须接受一个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工作原理大致相同,但我不会在这里提供一个人为的例子。我正在使用所有这三个没有问题,替换我的旧-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 在堆中有复制的块。这是安全的。

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

因此,您必须 Block_copy 块。

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

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

Block_release(myBlock);
于 2011-04-01T07:40:40.927 回答
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);
于 2014-07-09T23:38:45.240 回答
-1

认为如果你想让它继续存在,你必须复制它(当你不想让它再调用自己时释放它)。

于 2011-03-22T16:56:28.053 回答