web-dev-qa-db-ja.com

Objective-C、UIイベントを使用してディスパッチキューをキャンセルする

シナリオ:

  • ユーザーがボタンをタップして、アドレス帳の変更を要求します。
  • この変更を開始するためのメソッドが呼び出され、アラートビューが表示されます。
  • アラートビューを表示し、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, ^{});
    

ここで、いつでも(もちろん、アドレス帳を保存する前に)プロセスをキャンセルできるようにしたいのです。したがって、彼がアラートシートのキャンセルボタンをタップしたときに、ディスパッチブロックにアクセスし、特定のBOOLを設定してプロセスを停止し、アドレス帳を元に戻します。

問題は、それができないということです!すべての変数は一度だけコピーされるため、ブロックにアクセスしてその中の変数を変更することはできません。実行中のブロック内の変数の変更は、ブロックには表示されません。

要約すると、UIイベントを使用して進行中の操作を停止するにはどうすればよいですか?

更新:

プロセスのコード:

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

}

Alertview(自分の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行をコメントアウトすると、YESに変更されたときに_cancelModificationは無視されます。

15

__blockを使用してBOOLを宣言すると、ブロック実行の外部で変更でき、ブロックに新しい値が表示されます。詳細については、 ドキュメント を参照してください。

例:

@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
20
Kurt Revis

アプローチ1

「キャンセル可能な」ブロックを返すカスタム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");
}

アプローチ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");
    });
}
7
vilanovi

次のコードサンプルを状況に適用してみてください。

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