30

Is there a way to cancel dispatch_after() scheduled for some time in future, and haven't fired so far? I'm trying to make something like a scheduler for updates from server, and this method is just like I want, but, I'd love to cancel and re-schedule it at some point. Is it possible at all or I have to fallback and use NSTimer?

Oleg Shanyuk
  • 1,296
  • 2
  • 15
  • 26

4 Answers4

26

There is NO way to prevent a dispatch_block from executing once it has been dispatch to it's queue, meaning that your dispatch_after cannot be canceled. Only option is to add in your block a condition to be checked at runtime to prevent execution. ie.

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^ {
if(self.shouldExecuteDispatchBlock)
{ // do your stuff }  });
Florian Burel
  • 3,408
  • 1
  • 19
  • 20
  • Hmmm so until the next time for execution of this block comes up (and it's set to be cancelled), I guess it would hog all the memory/objects until then. Maybe a problem if your cycles are like 60 minutes? :-) – Jonny Jul 03 '15 at 05:33
  • Yes and no. The block copy the memory, wich mean if you refere to a int value in your block, this one will be copied and will stay in memory as a duplicate for as long as the block live. But for object, it does not duplicate them, it copy the adress. MEaning if you refere an image, you will have one UIImage object in the heap but the block will only retain a pointer value (8 bytes in arch64) so, no biggie. Of course the image will be retain in memory untill the block goes out, so it won't be clean by arc. – Florian Burel Jul 09 '15 at 12:39
  • I have turned this into a extension for DispatchQueue: https://github.com/nrbrook/DispatchAfterCancellable – Nick Sep 13 '16 at 00:29
  • 4
    Apple introduced `dispatch_block_cancel` in iOS 8. It asynchronously cancels blocks created with `dispatch_block_create`. https://developer.apple.com/reference/dispatch/1431058-dispatch_block_cancel?language=objc – Heath Borders Mar 03 '17 at 03:54
19

OK, so, with all answers collected, and possible solutions, seems like the best one for this case (preserving simplicity) is calling performSelector:withObject:afterDelay: and cancelling it with cancelPreviousPerformRequestsWithTarget: call when desired. In my case - just before scheduling next delayed call:

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

[self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay];
Oleg Shanyuk
  • 1,296
  • 2
  • 15
  • 26
6

For this purpose i used this class:

https://github.com/SebastienThiebaud/dispatch_cancelable_block

you can call a cancel() function to revoke the execution of what's in the block.

Zoltan Varadi
  • 2,468
  • 2
  • 34
  • 51
3

Use a dispatch timer source (that is what dispatch_after uses internally anyway).

A dispatch timer source can be canceled or its timer parameters changed after creation.

das
  • 3,651
  • 17
  • 19