TIL You Can Cancel Enqueued GCD Blocks

Today I learned that you can cancel a delayed dispatch_block_t with the new dispatch_block_cancel (available since OS X 10.10/iOS 8.0). Thanks Matt for the post – here’s a Swift example:

let work = dispatch_block_create(0) { print("Hello!") }

# Execute after 10s
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(10 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue(), work)

dispatch_block_cancel(work)
# Will never print "Hello!"

Note: canceling doesn’t work if the block is being executed.

If I knew that this API existed, I might not have used the very cumbersome approach from below in Move!.

Super-Weird Legacy Version of a Cancelable Delayed Block

For historic purposes, here’s an adaptation of the cancelable dispatch block you may find on the internet that I once have adapted for Swift:

typealias CancelableDispatchBlock = (cancel: Bool) -> Void

func dispatch(
        cancelableBlock block: dispatch_block_t,
        atDate date: NSDate
        ) -> CancelableDispatchBlock? {

    // Use two pointers for the same block handle to make
    // the block reference itself.
    var cancelableBlock: CancelableDispatchBlock? = nil

    let delayBlock: CancelableDispatchBlock = { cancel in

        if !cancel {
            dispatch_async(dispatch_get_main_queue(), block)
        }

        cancelableBlock = nil
    }

    cancelableBlock = delayBlock

    let interval = Int64(date.timeIntervalSinceNow)
    let delay = interval * Int64(NSEC_PER_SEC)

    dispatch_after(dispatch_walltime(nil, delay), dispatch_get_main_queue()) {

        guard let cancelableBlock = cancelableBlock else { return }

        cancelableBlock(cancel: false)
    }

    return cancelableBlock
}

func cancelBlock(block: CancelableDispatchBlock?) {
    guard let block = block else { return }

    block(cancel: true)
}

The trick is this: the delayed block delayBlock: CancelableDispatchBlock captures its context where a reference to cancelableBlock is included – but not set yet. Then you make the reference point to the delayBlock itself.

The actual canceling is a fake, though. The block is still called. It aborts early if the cancel parameter is true, though.