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