Асинхронно распределяемые рекурсивные блоки - PullRequest
10 голосов
/ 22 марта 2011

Предположим, я запускаю этот код:

__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 ]

16 голосов
/ 06 февраля 2013

При попытке решить эту проблему я обнаружил фрагмент кода, который решает большую часть проблем, связанных с рекурсивным блоком. Я не смог найти источник снова, но все еще имею код:

// 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 допускает блоки без аргументов. Его можно переписать для блоков с одним или несколькими аргументами. Использование этой конструкции упрощает управление памятью, например, нет шансов на сохранение цикла.

5 голосов
/ 21 сентября 2013

Мое решение было взято полностью из решения Берика, поэтому он получает всю заслугу здесь.Я просто почувствовал, что для проблемного пространства «рекурсивных блоков» нужна более общая структура (которую я не нашел в другом месте), в том числе для асинхронного случая, который описан здесь.

Использование этих трех первых определенийделает возможным использование четвертого и пятого методов - которые просто примеров - невероятно простой, надежный и (я считаю) безопасный для памяти способ вернуть любой блок в произвольные пределы.

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,который должен быть установлен в какой-то момент для выхода из рекурсии (знакомый шаблон в некоторых итераторах блоков Какао).

- (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:.

4 голосов
/ 01 апреля 2011

Похоже, что нет никаких проблем, кроме переменной задержки. Блок всегда использует одно и то же время, которое генерируется в строке 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);
};

EDIT:

Я понимаю.

Блок хранится в стеке литералом блока. Переменная myBlock заменяет адрес блока в стеке.

Сначала dispatch_after скопировал блок из переменной myBlock, которая является адресом в стеке. И этот адрес действителен в это время. Блок находится в текущей области видимости.

После этого блок выходит из области видимости. Переменная myBlock имеет неверный адрес в данный момент. dispatch_after имеет скопированный блок в куче. Это безопасно.

И затем второй dispatch_after в блоке пытается скопировать из переменной myBlock недопустимый адрес, поскольку блок в стеке уже выделен. Он выполнит поврежденный блок в стеке.

Таким образом, вы должны Block_copy блок.

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

И не забывайте Block_release блок, когда он вам больше не нужен.

Block_release(myBlock);
0 голосов
/ 10 июля 2014

Выберите пользовательский источник отправки.

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);
0 голосов
/ 22 марта 2011

Я думаю вы должны скопировать блок, если хотите, чтобы он оставался (освобождая его, когда вы больше не хотите, чтобы он сам вызывался).

...