「取消」 GCD 延时队列

在 iOS SDK 里面是无法取消提交的任务的,这里实现一个可以取消的例子。

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

dispatch_after 的作用是在指定的队列,指定的时间执行任务,一般用在需要延时处理的场景。

然而它的延时并非完全的延时,延时任务在 dispatch_after 调用的时候,就已经提交给操作系统,之后操作系统在指定时间执行这个任务。所以这个任务在提交之后,执行之前,在某些时候即使修改内部的数据,也不会影响执行的结果。

例如:

    int i = 0;
    NSTimeInterval time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        printf("i = %d", i);
    });
    i = 2;
    
    // 输出: i = 0

在任务 block 中,捕获了当前 i 的瞬时值,之后对它进行修改,不会影响执行的结果。这是因为 i 作为局部变量,是分配在栈上的,在 block 还没有执行之前,i 已经出栈回收了。

如果一定想修改 block 内的 i 可以这样声明:

__block int i = 0;

这时输出的结果为 i = 2。

在变量前加上 __block 时,会将变量拷贝到堆上,而堆内存在线程之间可以共享。具体的细节可以参考 block 的实现原理

dispatch_after 在 Objective-C 中属于 C 的 API,而在 Swift 中被封装成对象,iOS8 之前是没有取消的功能的,在 iOS8 之后有加入 DispatchWorkItem 的概念,支持取消 block 的执行。

在 iOS8 之前其实我们可以自定义「取消」block 。

extension DispatchQueue {
    typealias Task = (_ cancel: Bool) -> Void
    func delay(_ time: TimeInterval, task: @escaping ()->()) -> Task? {
        
        func dispatch_later(_ block: @escaping ()->()) {
            let t = DispatchTime.now() + time
            self.asyncAfter(deadline: t, execute: block)
        }
        
        var closure: (()->Void)? = task
        var result: Task?
        
        let delayedClosure: Task = { cancel in
            if let internalClosure = closure {
                if cancel == false {
                    self.async(execute: internalClosure)
                }
            }
            closure = nil
            result = nil
        }
        
        result = delayedClosure
        
        dispatch_later {
            if let delayedClosure = result {
                delayedClosure(false)
            }
        }
        
        return result
    }
    
    func cancel(_ task: Task?) {
        task?(true)
    }
}

使用:

let task =  DispatchQueue.main.delay(3) {
    print("延迟三秒输出")
}
        
DispatchQueue.main.cancel(task)

点击这里查看 Objective-C 版本。

你可能感兴趣的:(「取消」 GCD 延时队列)