Запретить выполнение фоновой задачи dispatch after()
это моя проблема. Когда мое приложение входит в фон, я хочу, чтобы оно выполняло функцию через определенный период времени. Вот что я делаю:--7-->
- (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;
все работает так, как должно, я вижу, что консоль печатает привет сразу после 30 секунд. Но я не хочу!--4--> для выполнения, если приложение выходит на передний план до 30 секунд. Так что мне нужно отменить его. Вот как я это делаю:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
isRunningInBackground = NO;
[self stopBackgroundExecution];
}
- (void)stopBackgroundExecution
{
[[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
taskIdentifier = UIBackgroundTaskInvalid;
}
но, к сожалению, это не отменяет doSomething
, он все еще выполняется. Что я делаю не так? Как отменить эту функцию?
9 ответов
зачем вообще использовать GCD? Вы можете просто использовать NSTimer
и аннулировать его, когда ваше приложение возвращается в foregound.
немного другой подход
Итак, со всеми собранными ответами и возможными решениями, кажется, что лучшим для этого случая (сохраняя простоту) является вызов performSelector:withObject:afterDelay:
и отменить его с cancelPreviousPerformRequestsWithTarget:
звонок при желании. В моем случае - непосредственно перед планированием следующего отложенного звонка:
[NSObject cancelPreviousPerformRequestsWithTarget: self selector:@selector(myDelayedMethod) object: self];
[self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay];
Я ответил на вопрос об отмене dispatch_after
здесь. Но когда я google, чтобы найти решение, он также возвращает меня в эту тему, поэтому...
iOS 8 и OS X Yosemite введены dispatch_block_cancel
, которые позволяют отменить блок, прежде чем они начнут выполняться. Вы можете просмотреть подробную информацию об этом ответе здесь
используя dispatch_after
получите выгоду от использования переменных, которые вы создали в этой функции, и выглядите бесшовными. Если вы используете NSTimer
затем вы должны создать Selector
и отправить переменные, которые вам нужны в userInfo
или превратить эти переменные в глобальные переменные.
этот ответ должен быть размещен здесь: отменить метод dispatch_after ()?, но это закрыто как дубликат (это действительно не так). Во всяком случае, это место, которое google возвращает для "dispatch_after cancel", поэтому...
этот вопрос довольно фундаментален, и я уверен, что есть люди, которые хотят по-настоящему общее решение, не прибегая к различным спецификациям платформы, таким как таймеры runloop, экземпляры, содержащие логические значения и/или тяжелую магию блоков. GCD может использоваться как обычный C библиотека и может не быть такой вещи, как таймер в целом.
к счастью, есть способ отменить любой блок отправки в любой схеме жизни.
- мы должны прикрепить динамический дескриптор к каждому блоку, который мы передаем dispatch_after (или dispatch_async, не имеет значения).
- этот дескриптор должны существует до тех пор, пока блок не будет фактически уволен.
- управление памятью для этого дескриптора не так очевидно – если блок освобождает дескриптор, то мы можем разыменовать болтающийся указатель позже, но если мы освободим его, блок может сделать это позже.
- Итак, мы должны передать право собственности на спрос.
- есть 2 блока – один представляет собой блок управления это срабатывает в любом случае, а второй -грузоподъемностью это может быть отменено.
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, которые сохраняют фактический массив блоков для отправки и удаления их при необходимости.
- вы можете заметить, что shouldCall и shouldFree действительно такой же перевернутый флаг. Но ваш экземпляр владельца может передать право собственности и даже- [dealloc] сам, фактически не отменяя блоки полезной нагрузки, если они не зависят от "себя" или других данных, связанных с владельцем. Это может реализовано с дополнительным аргументом shouldCallAnyway для dispatch_cancel_h ().
- предупреждение: этому решению также не хватает синхронизации флагов didXYZ и может вызвать гонку между блоком управления и процедурой отмены. Используйте OSAtomicOr32Barrier() & co для синхронизации.
endBackgroundTask
не отменяет фоновую задачу. Он сообщает системе, что ваша фоновая задача завершена. Поэтому вы должны назвать это после"что-то делать". Чтобы предотвратить doSomething
выполняется, если ваше приложение снова на переднем плане, вы можете использовать isRunningInBackground
флаг:
dispatch_after(popTime, dispatch_get_global_queue(...), ^(void) {
if (isRunningInBackground) {
[self doSomething];
}
[[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
});
Я думаю, вы не можете отменить его, но вы можете проверить состояние задачи перед выполнением doSomething
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
if(taskIdentifier != UIBackgroundTaskInvalid) {
[self doSomething];
}
});
вы можете полностью отменить его с помощью флага. Я написал небольшую функцию для этого, в основном мы передаем указатель 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];
}
}
С 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()
}
Это несколько более общий ответ, хотя я думаю, что он все еще отвечает на ваш вопрос достаточно хорошо. Вместо "isRunningInBackground" сохраните время последнего backgrounded/foregrounded; используйте время, когда вы были backgrounding в качестве локальной переменной для 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 at время они должны начать.