This answer must be posted here: cancel dispatch_after() method?, but that is closed as a duplicate (it really isn't). Anyway, this is a place that google returns for "dispatch_after cancel", so...
This question is pretty fundamental and I'm sure there are people who want a truly generic solution without resorting to various platform-specifics like runloop timers, instance-contained booleans and/or heavy block magic. GCD may be used as a regular C library and there may be no such thing as a timer all in all.
Luckily, there is a way to cancel any dispatch block in any lifetime scheme.
- We have to attach a dynamic handle to each block we pass to dispatch_after (or dispatch_async, not really matters).
 
- This handle must exist until the block is actually fired.
 
- Memory management for this handle is not so obvious – if block frees the handle, then we may dereference dangling pointer later, but if we free it, block may do that later.
 
- So, we have to pass ownership on demand.
 
- There are 2 blocks – one is a control block that fires anyway and second is a payload that may be canceled.
 
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
    }
}
That's it.
The main point is that "owner" should collect handles until it doesn't need them anymore. dispatch_cancel_h() works as a [potentially deferred] destructor for a handle.
C owner example:
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 example:
- (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.
}
Notes:
- dispatch_after() originally retains the queue, so it will exist until all control blocks are executed.
 
- async_handles are freed if payload is cancelled (or owner's lifetime was over) AND control block was executed.
 
- async_handle's dynamic memory overhead is absolutely minor compared to dispatch_after()'s and dispatch_queue_t's internal structures, which retain an actual array of blocks to be submitted and dequeue them when appropriate.
 
- You may notice that shouldCall and shouldFree is really the same inverted flag. But your owner instance may pass the ownership and even -[dealloc] itself without actually canceling payload blocks, if these do not depend on "self" or other owner-related data. This could be implemented with additional shouldCallAnyway argument to dispatch_cancel_h().
 
- Warning note: this solution also lacks synchronization of didXYZ flags and may cause a race between control block and cancellation routine. Use OSAtomicOr32Barrier() & co to synchronize.