2012-09-18 94 views
10

这是我的问题。当我的应用程序进入后台时,我希望它在一段时间后执行一项功能。这是我做的:防止执行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,它仍然执行。我究竟做错了什么?我如何取消该功能?

回答

13

为什么要使用GCD?您可以使用NSTimer,并在您的应用返回到前台时使其无效。

+0

你是对的,那是更好的解决方案! –

+0

非常感谢!很简单!我应该自己想过 –

3

endBackgroundTask不取消后台任务。它告诉系统您的后台任务已完成。所以你应该在“做某事”之后调用它。为了防止doSomething被执行,如果你的应用程序是在前台再次,你可以用你的isRunningInBackground标志:

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

这是正确的答案 – juancazalla

2

我想你不能取消它,但你可以在执行DoSomething的

前检查任务状态
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) 
    { 

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

    }); 
11

有点不同的方法 OK,所以,用收集到的所有答案,可能的解决方案,似乎是最好的一个针对这种情况(保留简单)呼吁performSelector:withObject:afterDelay:并在需要时用电话cancelPreviousPerformRequestsWithTarget:取消它。在我的情况 - 安排下一之前延迟呼叫:

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

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

这个答案被张贴在这里:cancel dispatch_after() method?,但(这真不是)被关闭为重复。无论如何,这是一个地方,谷歌返回“dispatch_after取消”,所以...

这个问题是非常基本的,我敢肯定有人想要一个真正的通用解决方案,而不诉诸各种特定于平台的runloop计时器,实例包含的布尔值和/或重块模块。 GCD可以用作一个普通的C库,并且可能根本就没有定时器之类的东西。

幸运的是,有一种方法可以取消任何生命周期方案中的任何调度块。

  1. 我们必须为每个传递给dispatch_after(或dispatch_async,实际上并不重要)的块附加一个动态句柄。
  2. 此句柄必须存在,直到块被实际触发。
  3. 这个句柄的内存管理并不那么明显 - 如果块释放句柄,那么我们可能会在稍后解除引用悬挂指针,但如果我们释放它,块可能会在稍后执行。
  4. 所以,我们必须通过所有权的需求。
  5. 有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。
  • 与dispatch_after()和dispatch_queue_t的内部结构相比,async_handle的动态内存开销是绝对较小的,内部结构保留要提交的实际块数组并在适当时将它们出队。
  • 您可能会注意到shouldCall和shouldFree确实是相同的倒转标志。但是,您的所有者实例可能会传递所有权,甚至是 - [dealloc]本身,而不会实际取消有效载荷块,如果这些不依赖于“自我”或其他所有者相关数据。这可以通过dispatch_cancel_h()的其他shouldCallAnyway参数来实现。
  • 警告::此解决方案也缺少didXYZ标志的同步,并可能导致控制块和取消例程之间的竞争。使用OSAtomicOr32Barrier()& co进行同步。
+0

注意:答案中的代码是为ARC编写的。在MRC或纯C中,有效负载应该在控制块的dispatch_after_h()和Block_release'd中明确地进行Block_retain,以修复过早的重新分配。 – user3125367

1

你完全可以用标志取消它。我写了一个小函数来完成它,基本上我们传递一个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]; 
    } 
} 
7

我回答了这个问题关于取消dispatch_afterhere。但是,当我谷歌找到一个解决方案,它也让我回到这个线程,所以...

iOS 8和OS X优胜美地引入dispatch_block_cancel,允许您在开始执行之前取消块。您可以查看关于该答案的详细信息here

使用dispatch_after获得有关使用您在该函数中创建的变量的好处并且看起来无缝。如果您使用NSTimer,那么您必须创建一个Selector并将需要的变量发送到userInfo或将该变量转换为全局变量。

+0

这实际上是现在正确的答案,因为原来的答案基本上是“不要那样做,而是这样做。” –

0

这是一个更通用的回应,虽然我认为它仍能很好地回答你的问题。而不是“isRunningInBackground”保持你最后一次背景/前景的时间;使用您作为dispatch_after的局部变量背景的时间。在调用doSomething之前检查你的dispatch_after。我的下面更具体的问题....

我做了一大堆动画,需要在不同时间启动,如果我使用setBeginTime,同时确保模型层更新到演示文稿,在正确的时间层,等等...所以我开始使用dispatch_after,除了不能“取消”它们(这对我来说非常重要,特别是当我想重新开始一系列动画时)。

我对我的UIView实例保持CFTimeInterval startCalled;,然后我-(void) start里面我有:

startCalled = CACurrentMediaTime(); 
CFTimeInterval thisStartCalled = startCalled; 

在每个dispatch_after块的开始,我则有:

if (thisStartCalled != startCalled) return; 

这让我可以一次性设置所有内容,但只需在我们的模型图层更新内部的CATransaction块,开始。

1

由于的iOS 10夫特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() 
}