这是我的问题。当我的应用程序进入后台时,我希望它在一段时间后执行一个功能。这就是我的工作:
- (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秒后就打印了HELLO。但是如果应用程序进入前景直到30秒结束,我不希望执行doSomething
。所以我需要取消它。这就是我这样做的方式:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
isRunningInBackground = NO;
[self stopBackgroundExecution];
}
- (void)stopBackgroundExecution
{
[[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
taskIdentifier = UIBackgroundTaskInvalid;
}
但不幸的是它没有取消doSomething
,它仍在执行。我究竟做错了什么?如何取消该功能?
答案 0 :(得分:13)
为什么甚至使用GCD?您可以使用NSTimer
,并在应用返回到前台时使其无效。
答案 1 :(得分:12)
有点不同的方法
好的,所以,收集了所有答案,并且可能的解决方案似乎是这种情况下最好的解决方案(保持简洁性)是调用performSelector:withObject:afterDelay:
并在需要时使用cancelPreviousPerformRequestsWithTarget:
调用取消它。就我而言 - 就在安排下一次延迟通话之前:
[NSObject cancelPreviousPerformRequestsWithTarget: self selector:@selector(myDelayedMethod) object: self];
[self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay];
答案 2 :(得分:9)
我回答了有关取消dispatch_after
here的问题。但是当我谷歌找到解决方案时,它也会让我回到这个主题,所以......
iOS 8和OS X Yosemite推出dispatch_block_cancel
,允许您在开始执行之前取消阻止。您可以查看有关该答案的详细信息here
使用dispatch_after
可以获得有关使用在该函数中创建的变量并且看起来无缝的好处。如果您使用NSTimer
,则必须创建Selector
并将所需的变量发送到userInfo
或将变量转换为全局变量。
答案 3 :(得分:6)
这个答案必须在这里发布:cancel dispatch_after() method?,但这是一个重复的关闭(它真的不是)。无论如何,这是一个谷歌返回的地方" dispatch_after取消",所以......
这个问题非常重要,我确信有些人想要一个真正通用的解决方案,而不需要使用各种特定平台,如runloop计时器,实例包含的布尔值和/或重块魔术。 GCD可以用作常规C库,并且可能根本没有计时器。
幸运的是,有一种方法可以取消任何生命周期计划中的任何调度块。
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.
}
注意:
答案 4 :(得分:3)
endBackgroundTask
不会取消后台任务。它告诉系统您的后台任务已完成。所以你应该在“做某事”之后再打电话。如果您的应用再次位于前台,为了防止执行doSomething
,您可以使用isRunningInBackground
标记:
dispatch_after(popTime, dispatch_get_global_queue(...), ^(void) {
if (isRunningInBackground) {
[self doSomething];
}
[[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
});
答案 5 :(得分:3)
由于 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()
}
答案 6 :(得分:2)
我认为您无法取消它,但您可以在执行doSomething之前检查任务状态
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
if(taskIdentifier != UIBackgroundTaskInvalid) {
[self doSomething];
}
});
答案 7 :(得分:1)
你绝对可以用旗帜取消它。我写了一个小函数来完成它,基本上我们传递一个BOOL指针来控制块是否被取消。
openssl rsa -in openssl.priv -pubout >openssl.pub
答案 8 :(得分:1)
https://developer.apple.com/documentation/dispatch/1431058-dispatch_block_cancel
dispatch_block_cancel 异步取消指定的调度块。
void dispatch_block_cancel(dispatch_block_t block);
(iOS 8.0 +,macOS 10.10 +,tvOS 9.0 +,watchOS 2.0 +)
答案 9 :(得分:0)
这是一个更为通用的回应,虽然我认为它仍然可以很好地回答你的问题。而不是&#34; isRunningInBackground&#34;保持你上次背景/前庭的时间;将您作为背景的时间用作dispatch_after的局部变量。在调用doSomething之前检查dispatch_after内部。我下面更具体的问题....
我做了一大堆需要在不同时间启动的动画,如果我使用setBeginTime同时确保模型层在正确的时间更新到表示层等,那么它们会互相踩踏,等等。 ..所以我开始使用dispatch_after,除了没有&#t; t&#34;取消&#34;它们(这对我来说很重要,特别是当我想重新启动一系列动画时)。
我在我的UIView实例上保留CFTimeInterval startCalled;
,然后在我的-(void) start
内部:
startCalled = CACurrentMediaTime();
CFTimeInterval thisStartCalled = startCalled;
在每个dispatch_after
块的开头,我有:
if (thisStartCalled != startCalled) return;
这让我可以一次性设置所有内容,但只有我们的模型图层在他们应该开始的时间 的CATransaction块内更新。
答案 10 :(得分:0)
我使用以下方法在不阻塞主线程的情况下对搜索进行去抖动。
创建一个全局变量来存储块
dispatch_block_t searchBlock;
检查它是否存在并取消它(仅当它尚未发送时)
if (searchBlock) {
dispatch_block_cancel(searchBlock);
}
定义块
searchBlock = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{
//Do Something
});
执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), serialQueue, searchBlock);