Scenario:
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, ^{});
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.
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?
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
- (void) alertViewProgressCancel:(ASAlertViewProgress *)alertView { // This is a private lib.
if (cancelledPtr)
{
NSLog(@"stopping");
*cancelledPtr = YES;
}
}
In interface, I declare
BOOL* cancelledPtr;
Update 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!
If you declare your BOOL
using __block
, then it can be changed outside of the block execution, and the block will see the new value. See the documentation for more details.
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 self
and will access the ivar via that. No need for __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
Create a custom dispatch_async method that returns a "cancelable" block.
// 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");
}
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");
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With