ios Objective-C,使用 UI 事件取消调度队列

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10066897/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 17:45:38  来源:igfitidea点击:

Objective-C, cancel a dispatch queue using UI event

iphoneobjective-ciosconcurrencygrand-central-dispatch

提问by Abdalrahman Shatou

Scenario:

设想:

  • User taps a button asking for some kind of modification on address book.
  • A method is called to start this modification and an alert view is shown.
  • In order to show the alert view and keep the UI responsive, I used dispatch_queue:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                     dispatch_sync(dispatch_get_main_queue(), ^{
                       // Show the alert view
                     });
                   });
    
  • Start the process of address book modification using:

    dispatch_async(modifyingAddressBookQueue, ^{});
    
  • 用户点击一个按钮,要求对地址簿进行某种修改。
  • 调用方法来启动此修改并显示警报视图。
  • 为了显示警报视图并保持 UI 响应,我使用了 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, ^{});
    

Now, I want to provide the user with the ability to cancel the process anytime (of course before saving the address book). So when he taps the cancel button in the alert sheet, I want to access the dispatch block, set some certain BOOL to stop the process and revert the address book.

现在,我想为用户提供随时取消过程的能力(当然在保存地址簿之前)。因此,当他点击警报表中的取消按钮时,我想访问调度块,设置一些特定的 BOOL 以停止该过程并恢复地址簿。

The problem is, you can't do that! you can't access the block and change any variable inside it since all variables are copied only once. Any change of variables inside the block while being executed won't be seen by the block.

问题是,你不能那样做!您无法访问该块并更改其中的任何变量,因为所有变量仅被复制一次。执行时块内变量的任何更改都不会被块看到。

To sum up: How to stop a going operation using a UI event?

总结一下:如何使用 UI 事件停止正在进行的操作?

Update:

更新:

The code for the process:

该过程的代码:

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

}

Code for alertview (my own lib) delegate

alertview(我自己的库)委托的代码

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


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

        *cancelledPtr = YES;
    }

}

In interface, I declare

在界面中,我声明

BOOL*   cancelledPtr;

Update 2:

更新 2:

It's getting really frustrating! for the following code

真的越来越不爽了!对于以下代码

for (NSMutableDictionary *contactDictionary in _contactArray) {

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

if _cancelModification is set to YES, the for loop is broken and that's OK. Once I comment out the NSLog line, the _cancelModification is neglected when it changes to YES!

如果 _cancelModification 设置为 YES,则 for 循环被破坏,没关系。一旦我注释掉 NSLog 行,_cancelModification 在更改为 YES 时被忽略!

回答by Kurt Revis

If you declare your BOOLusing __block, then it can be changed outside of the block execution, and the block will see the new value. See the documentationfor more details.

如果您声明BOOLusing __block,则可以在块执行之外对其进行更改,并且块将看到新值。有关更多详细信息,请参阅文档

An example:

一个例子:

@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

Alternatively, use an ivar in your class to store the BOOL. The block will implicitly make a copy of selfand will access the ivar via that. No need for __block.

或者,在您的类中使用 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

回答by vilanovi

Approach 1

方法一

Create a custom dispatch_async method that returns a "cancelable" block.

创建一个返回“可取消”块的自定义 dispatch_async 方法。

// 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: %@", success1?@"YES":@"NO");

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

Approach 2

方法二

Defining a macro for executing a block asynchronously if a condition is validated:

如果条件得到验证,则定义用于异步执行块的宏:

#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");
    });
}

回答by lingyfh

Try to apply the following code sample to your situation:

尝试将以下代码示例应用于您的情况:

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