Подтвердить что ты не робот

Предотвратить выполнение диспетчеризации dispatch_after()

Это моя проблема. Когда мое приложение входит в фон, я хочу, чтобы он выполнял функцию через определенный промежуток времени. Это то, что я делаю:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    isRunningInBackground = YES;

    taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];

    int64_t delayInSeconds = 30;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
    {
        [self doSomething];
    });
}

- (void)doSomething
{
   NSLog(@"HELLO");
}

taskIdentifier переменная объявляется в файле myAppDelegate.h следующим образом:

UIBackgroundTaskIdentifier taskIdentifier;

Все работает так, как предполагалось, я вижу, что консоль печатает HELLO только через 30 секунд. Но я не хочу, чтобы doSomething выполнялся, если приложение переходит на передний план до 30 секунд. Поэтому мне нужно отменить это. Вот как я это делаю:

- (void)applicationWillEnterForeground:(UIApplication *)application
{    
    isRunningInBackground = NO;
    [self stopBackgroundExecution];
}

- (void)stopBackgroundExecution
{
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
    taskIdentifier = UIBackgroundTaskInvalid;
}

Но, к сожалению, он не отменяет doSomething, он все еще выполняется. Что я делаю не так? Как мне отменить эту функцию?

4b9b3361

Ответ 1

Почему даже использовать GCD? Вы можете просто использовать NSTimer и сделать это недействительным, когда ваше приложение вернется к форварду.

Ответ 2

Немного другой подход Хорошо, поэтому, со всеми собранными ответами и возможными решениями, кажется лучшим для этого случая (сохраняя простоту), вызывает performSelector:withObject:afterDelay: и отменяя его при вызове cancelPreviousPerformRequestsWithTarget: по желанию. В моем случае - перед расписанием следующего отложенного вызова:

[NSObject cancelPreviousPerformRequestsWithTarget: self selector:@selector(myDelayedMethod) object: self];

[self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay];

Ответ 3

Я ответил на вопрос об отмене dispatch_after здесь. Но когда я google, чтобы найти решение, он также возвращает меня к этой теме, поэтому...

iOS 8 и OS X Yosemite представили dispatch_block_cancel, которые позволяют отменить блок до того, как они начнут выполняться. Вы можете просмотреть подробные сведения об этом ответе здесь

Используя dispatch_after, получите выгоду от использования переменных, которые вы создали в этой функции, и посмотрите без проблем. Если вы используете NSTimer, тогда вы должны создать Selector и отправить переменные, которые вам нужны, в userInfo или превратить эти переменные в глобальные переменные.

Ответ 4

Этот ответ должен быть опубликован здесь: отменить метод dispatch_after()?, но он закрыт как дубликат (это действительно не так). В любом случае, это место, которое google возвращает для отправки "dispatch_after cancel", поэтому...

Этот вопрос довольно фундаментален, и я уверен, что есть люди, которые хотят действительно универсальное решение, не прибегая к различным особенностям платформы, таким как таймеры runloop, экземпляры с буферами и/или тяжелая блочная магия. GCD может использоваться как обычная библиотека C, и вообще не может быть такой вещи, как таймер.

К счастью, есть способ отменить любой блок отправки в любой схеме жизни.

  • Мы должны приложить динамический дескриптор к каждому блоку, который мы передаем dispatch_after (или dispatch_async, не имеет значения).
  • Этот дескриптор должен существовать до тех пор, пока блок не будет запущен.
  • Управление памятью для этого дескриптора не так очевидно - если блок освобождает дескриптор, тогда мы можем разыгрывать указатель на свидание позже, но если мы его освободим, блок может сделать это позже.
  • Таким образом, мы должны передавать собственность по требованию.
  • Есть два блока: один - это блок управления, который срабатывает в любом случае, а второй - полезная нагрузка, которая может быть отменена.

struct async_handle {
    char didFire;       // control block did fire
    char shouldCall;    // control block should call payload
    char shouldFree;    // control block is owner of this handle
};

static struct async_handle *
dispatch_after_h(dispatch_time_t when,
                 dispatch_queue_t queue,
                 dispatch_block_t payload)
{
    struct async_handle *handle = malloc(sizeof(*handle));

    handle->didFire = 0;
    handle->shouldCall = 1; // initially, payload should be called
    handle->shouldFree = 0; // and handles belong to owner

    payload = Block_copy(payload);

    dispatch_after(when, queue, ^{
        // this is a control block

        printf("[%p] (control block) call=%d, free=%d\n",
            handle, handle->shouldCall, handle->shouldFree);

        handle->didFire = 1;
        if (handle->shouldCall) payload();
        if (handle->shouldFree) free(handle);
        Block_release(payload);
    });

    return handle; // to owner
}

void
dispatch_cancel_h(struct async_handle *handle)
{
    if (handle->didFire) {
        printf("[%p] (owner) too late, freeing myself\n", handle);
        free(handle);
    }
    else {
        printf("[%p] (owner) set call=0, free=1\n", handle);
        handle->shouldCall = 0;
        handle->shouldFree = 1; // control block is owner now
    }
}

Что это.

Главное, что "владелец" должен собирать ручки, пока они им больше не понадобятся. dispatch_cancel_h() работает как дескриптор [потенциально отложенного] для дескриптора.

Пример владельца C:

size_t n = 100;
struct after_handle *handles[n];

for (size_t i = 0; i < n; i++)
    handles[i] = dispatch_after_h(when, queue, ^{
        printf("working\n");
        sleep(1);
    });

...

// cancel blocks when lifetime is over!

for (size_t i = 0; i < n; i++) {
    dispatch_cancel_h(handles[i]);
    handles[i] = NULL; // not our responsibility now
}

Objective-C Пример ARC:

- (id)init
{
    self = [super init];
    if (self) {
        queue = dispatch_queue_create("...", DISPATCH_QUEUE_SERIAL);
        handles = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)submitBlocks
{
    for (int i = 0; i < 100; i++) {
        dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (random() % 10) * NSEC_PER_SEC);

        __unsafe_unretained id this = self; // prevent retain cycles

        struct async_handle *handle = dispatch_after_h(when, queue, ^{
            printf("working (%d)\n", [this someIntValue]);
            sleep(1);
        });
        [handles addObject:[NSValue valueWithPointer:handle]];
    }
}

- (void)cancelAnyBlock
{
    NSUInteger i = random() % [handles count];
    dispatch_cancel_h([handles[i] pointerValue]);
    [handles removeObjectAtIndex:i];
}

- (void)dealloc
{
    for (NSValue *value in handles) {
        struct async_handle *handle = [value pointerValue];
        dispatch_cancel_h(handle);
    }
    // now control blocks will never call payload that
    // dereferences now-dangling self/this.
}

Примечания:

  • dispatch_after() первоначально сохраняет очередь, поэтому она будет существовать до тех пор, пока не будут выполнены все блоки управления.
  • async_handles освобождаются, если полезная нагрузка отменяется (или время жизни владельца завершено) И был выполнен контрольный блок.
  • Накладные расходы динамической памяти async_handle абсолютно незначительны по сравнению с внутренними структурами dispatch_after() и dispatch_queue_t, которые сохраняют фактический массив блоков, подлежащих отправке, и деактивируют их, когда это необходимо.
  • Вы можете заметить, что mustCall и shouldFree - это действительно тот же перевернутый флаг. Но ваш экземпляр владельца может передать право собственности и даже - [dealloc] сам, фактически не отменяя блоки полезной нагрузки, если они не зависят от "я" или других данных, связанных с владельцем. Это может быть реализовано с дополнительным аргументом toCallAnyway для dispatch_cancel_h().
  • Предупреждение. Это решение также не имеет синхронизации флагов didXYZ и может вызывать гонку между блоком управления и процедурой отмены. Используйте OSAtomicOr32Barrier() и co для синхронизации.

Ответ 5

endBackgroundTask не отменяет фоновой задачи. Он сообщает системе, что ваша фоновая задача завершена. Поэтому вы должны называть это "делать что-то". Чтобы предотвратить выполнение doSomething, если ваше приложение снова находится на переднем плане, вы можете использовать флаг isRunningInBackground:

dispatch_after(popTime, dispatch_get_global_queue(...), ^(void) {
    if (isRunningInBackground) {
        [self doSomething];
    }
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
});

Ответ 6

Я думаю, вы не можете отменить его, но вы можете проверить состояние задачи перед выполнением doSomething

dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
  {

    if(taskIdentifier != UIBackgroundTaskInvalid) {
        [self doSomething];
    }

  });

Ответ 7

Вы можете полностью отменить его с помощью флага. Я написал небольшую функцию для этого, в основном мы передаем указатель BOOL для управления тем, отменен ли блок.

void dispatch_with_cancellation(void (^block)(), BOOL* cancellation) {
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
        if (!*cancellation) {
            block();
        }
    });
}

int main(int argc, char *argv[]) {
    @autoreleasepool {
        void (^block)() = ^{
            NSLog(@"%@", @"inside block");
        };
        BOOL cancellation;
        dispatch_with_cancellation(block, &cancellation);
        // cancel the block by setting the BOOL to YES.
        *&cancellation = YES;
        [[NSRunLoop currentRunLoop] run];
    }
}

Ответ 8

Так как iOS 10 и Swift 3 GCD DispatchWorkItem являются отменными. Просто сохраните экземпляр для рабочего элемента и проверьте, не отменено ли оно и затем отмените его:

// Create a work item
let work = DispatchWorkItem {
    print("Work to be done or cancelled")
}

// Dispatch the work item for executing after 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: work)

// Later cancel the work item
if !work.isCancelled {
    print("Work:\(work)")
    dispatchPrecondition(condition: .onQueue(.main))
    work.cancel()
}

Ответ 9

Это несколько более общий ответ, хотя я думаю, что он по-прежнему отвечает на ваш вопрос достаточно хорошо. Вместо "isRunningInBackground" держите время, когда вы в последний раз задумывались/перед ним; используйте время, которое вы использовали в качестве локальной переменной для dispatch_after. Проверьте внутри своего dispatch_after перед вызовом doSomething. Моя более конкретная проблема ниже....

Я делаю длинную кучу анимаций, которые нужно запускать в разное время, и будут топать друг над другом, если бы я использовал setBeginTime, убедившись, что слой модели обновлен до уровня представления в нужное время и т.д. поэтому я начал использовать dispatch_after, за исключением того, что не мог "отменить" их (что важно для меня, особенно когда я хотел перезапустить серию анимаций).

Я сохраняю CFTimeInterval startCalled; в моем экземпляре UIView, а затем внутри моего -(void) start у меня есть:

startCalled = CACurrentMediaTime();
CFTimeInterval thisStartCalled = startCalled;

В начале каждого блока dispatch_after у меня есть:

if (thisStartCalled != startCalled) return;

Это позволяет мне настраивать все за один раз, но обновлять модули можно только внутри своих блоков CATransaction во время их начала.