Objective-C, отмените очередь отправки, используя событие пользовательского интерфейса

Сценарий:

  • Пользователь нажимает кнопку с просьбой о какой-либо модификации в адресной книге.
  • Вызывается метод для запуска этой модификации и отображается вид предупреждения.
  • Чтобы показать представление предупреждения и сохранить отзывчивость пользовательского интерфейса, я использовал dispatch_queue:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                     dispatch_sync(dispatch_get_main_queue(), ^{
                       // Show the alert view
                     });
                   });
    
  • Запустите процесс модификации адресной книги, используя:

    dispatch_async(modifyingAddressBookQueue, ^{});
    

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

Проблема в том, что вы не можете этого сделать! вы не можете получить доступ к блоку и изменить любую переменную внутри него, поскольку все переменные копируются только один раз. Любое изменение переменных внутри блока во время выполнения не будет видно блоку.

Подводя итог: как остановить текущую операцию с помощью события пользовательского интерфейса?

Update:

Код процесса:

- (void) startFixingModification {

    _fixContacts = YES;
    __block BOOL cancelled = NO;

    dispatch_queue_t modifyingAddressBookQueue;
    modifyingAddressBookQueue = dispatch_queue_create(sModifyingAddressBookQueueIdentifier,
                                                      NULL);

    dispatch_async(modifyingAddressBookQueue, ^{

        for (NSMutableDictionary *contactDictionary in _contactArray) {

            if (!cancelled) {
                break;
            }

            i = i + 1;

            BOOL didFixContact = [self fixNumberInContactDictionary:contactDictionary];
            if (!didFixContact) {
                _fixedNumbers = _fixedNumbers - 1;
            }

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                dispatch_sync(dispatch_get_main_queue(), ^{
                    [self setAlertViewProgress:i];
                });

            });
        }
    });

    cancelledPtr = &cancelled;

}

Код для оповещения (мой собственный lib) делегат

- (void) alertViewProgressCancel:(ASAlertViewProgress *)alertView { // This is a private lib.


    if (cancelledPtr)
    {
        NSLog(@"stopping");

        *cancelledPtr = YES;
    }

}

В интерфейсе я объявляю

BOOL*   cancelledPtr;

Обновление 2:

Это действительно расстраивает! для следующего кода

for (NSMutableDictionary *contactDictionary in _contactArray) {

            NSLog(@"%d", _cancelModification);
            if (_cancelModification) {
                break;
            }
}

если для параметра _cancelModification установлено значение YES, цикл for нарушен и это ОК. Когда я прокомментирую строку NSLog, _cancelModification пренебрегают, когда она изменится на YES!

Ответы

Ответ 1

Если вы объявляете свой BOOL с помощью __block, его можно изменить за пределами исполнения блока, и блок увидит новое значение. Подробнее см. документацию.

Пример:

@interface SNViewController ()
{
    BOOL*   cancelledPtr;
}

@end

@implementation SNViewController

- (IBAction)start:(id)sender
{
    __block BOOL cancelled = NO;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (!cancelled) {
            NSLog(@"running");
            sleep(1);
        }        
        NSLog(@"stopped");
    });

    cancelledPtr = &cancelled;
}

- (IBAction)stop:(id)sender
{
    if (cancelledPtr)
    {
        NSLog(@"stopping");

        *cancelledPtr = YES;
    }
}

@end

Кроме того, используйте ivar в своем классе для хранения BOOL. Блок будет неявно сделать копию self и получит доступ к ivar через это. Нет необходимости в __block.

@interface SNViewController ()
{
    BOOL   cancelled;
}

@end

@implementation SNViewController

- (IBAction)start:(id)sender
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (!cancelled) {
            NSLog(@"running");
            sleep(1);
        }        
        NSLog(@"stopped");
    });
}

- (IBAction)stop:(id)sender
{
    NSLog(@"stopping");
    cancelled = YES;
}

@end

Ответ 2

Подход 1

Создайте собственный метод dispatch_async, который возвращает блок "cancelable".

// The dispatch_cancel_block_t takes as parameter the "cancel" directive to suspend the block execution or not whenever the block to execute is dispatched. 
// The return value is a boolean indicating if the block has already been executed or not.
typedef BOOL (^dispatch_cancel_block_t)(BOOL cancelBlock);

dispatch_cancel_block_t dispatch_async_with_cancel_block(dispatch_queue_t queue, void (^block)())
{
    __block BOOL execute = YES;
    __block BOOL executed = NO;

    dispatch_cancel_block_t cancelBlock = ^BOOL (BOOL cancelled) {
        execute = !cancelled;
        return executed == NO;
    };

    dispatch_async(queue, ^{
        if (execute)
            block();
        executed = YES;
    });

    return cancelBlock;
}

- (void)testCancelableBlock
{
    dispatch_cancel_block_t cancelBlock = dispatch_async_with_cancel_block(dispatch_get_main_queue(), ^{
        NSLog(@"Block 1 executed");
    });

    // Canceling the block execution
    BOOL success1 = cancelBlock(YES);
    NSLog(@"Block is cancelled successfully: %@", [email protected]"YES":@"NO");

    // Resuming the block execution
    // BOOL success2 = cancelBlock(NO);
    // NSLog(@"Block is resumed successfully: %@", [email protected]"YES":@"NO");
}

Подход 2

Определение макроса для выполнения блока асинхронно, если условие проверено:

#define dispatch_async_if(queue,condition,block) \
dispatch_async(queue, ^{\
    if (condition == YES)\
        block();\
});

- (void)testConditionBlock
{
    // Creating condition variable
    __block BOOL condition = YES;

    dispatch_async_if(dispatch_get_main_queue(), condition, ^{
        NSLog(@"Block 2 executed");
    });

    // Canceling the block execution
    condition = NO;

    // Also, we could use a method to test the condition status
    dispatch_async_if(dispatch_get_main_queue(), ![self mustCancelBlockExecution], ^{
        NSLog(@"Block 3 executed");
    });
}

Ответ 3

Попробуйте применить следующий пример кода к вашей ситуации:

__block UIView * tempView = [[UIView alloc] initWithFrame:CGRectMake(50, 100, 220, 30)];
[tempView setBackgroundColor:[UIColor grayColor]];
[self.view addSubview:tempView];
[tempView release];

__block BOOL cancel = NO;
//点击之后就会开始执行这个方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    int i = 0;
    while (i < 1000000000 && cancel == NO) {
        i++;
    }
    NSLog(@"Task end: i = %d", i);
    //这个不会执行,因为在之前,gcd task已经结束
    [tempView removeFromSuperview];
});

//1s 之后执行这个方法
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    NSLog(@"A GCD Task Start");
    cancel = YES;
    [tempView setBackgroundColor:[UIColor blackColor]];
});