GCD和延时调用

代码

在下面我给出了一个日常里最通常会使用到的例子(说这个例子能覆盖到日常的GCD使用的50%以上也不为过),来展示一下Swift里的GCD调用会是什么样子:

    //  MARK: - GCD
    // 创建目标队列
    let workingQueue = DispatchQueue(label: "MyQueue")

    // 派发到刚创建的队列中
    workingQueue.async {
        // 在 workingQueue 中异步进行
        print("努力工作")
        Thread.sleep(forTimeInterval: 2) //模拟两秒的执行时间

        DispatchQueue.main.async {
            // 返回到主线程更新 UI
            print("结束工作, 更新 UI")
        }
    }

因为 UIKit 是只能在主线程工作的,如果我们在主线程进行繁重的工作的话,就会导致 app 出现“卡死”的现象:UI不能更新,用户输入无法响应等等,是非常糟糕的用户体验。为了避免这种情况的出现,对于繁重(如图像加滤镜等)或会很长时间才能完成的(如从网络下载图片)处理,我们 应该把它们放到后台线程进行,这样在用户看来UI还是可以交互的,也不会出现卡顿。在工作进行完成后,我们需要更新UI的话,必须回到主线程进行(牢记UI相关的工作都需要在主线程执行,否则可能发生不可预知的错误)。

GCD里有一个很好用的延时调用,那就是asyncAfter 。最简单的使用方法看起来是这样的:

    //  MARK: - 延时调用
    let time: TimeInterval = 2.0
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time) {
        print("2 秒后输出")
    }

在这里我们可以稍微将它封装的好用一些,最好再加上取消的功能。在iOS 8中GCD得到了惊人的进化,现在我们可以通过将一个闭包封装到 DispatchworkItem 对象中,然后对其发送 cancle ,来取消一个正在等待执行的 block 。取消一个任务这样的特性,这在以前是 NSOperation 的专利,但是现在我们使用 GCD 也能达到同样的目的了。这里我们不使用这个方式,而是通过捕获一个 cancel 标识变量来实现 delay call 的取消,整个封装也许有点长,但我还是推荐一读。

import Foundation

typealias Task = (_ cancle: Bool) -> Void

func delay(_ time: TimeInterval, task: @escaping () -> ()) -> Task? {

    // 嵌套函数
    func dispatch_later(block: @escaping () -> ()) {
        let t = DispatchTime.now() + time
        DispatchQueue.main.asyncAfter(deadline: t, execute: block)
    }


    var closure: (() -> Void)? = task
    var result: Task?

    let delayedClosure: Task = {
        cancel in
        if let internalColsure = closure {
            if cancel == false {
                DispatchQueue.main.async(execute: internalColsure)
            }
        }
        closure = nil
        result = nil
    }

    result = delayedClosure

    dispatch_later {
        if let delayedClosure = result {
            delayedClosure(false)
        }
    }

    return result

}

func cancel(_ task: Task?) {
    task?(true)
}

使用的时候就很简单了, 我们想在2秒之后干点啥的话:

delay(2) { print("2 秒后输出") }

想要取消的话,我们可以保留一个队 Task 的引用,然后调用 cancel :

    let task = delay(5) {
        print("拨打 110")
    }
    //仔细想想还是取消为妙
    cancel(task)

Lock

无并发,不编码。而只要一说到多线程或者并发的代码,我们可能就很难绕开对于锁的讨论。简单来说,为了在不同线程中安全地访问同一个资源,我们需要这些访问顺序进行。Cocoa和Objective-C中加锁的方式有很多,但是其中在日常开发中最常用的应该是 @syncchronized,这个关键字可以用来修饰一个变量,并为其自动加上和解除互斥锁。这样,可以保证变量在作用范围内不会被其他线程改变。举个例子,如果我们有一个方法接受参数,需要这个方法是线程安全的话,就需要在参数上加锁:

- (void)myMethod:(id)anObj {
    @synchronized (anObj) {
        // 在括号内 anObj 不会被其他线程改变
    }
}

如果没有锁的话,一旦 anObj 的内容被其他线程修改的话,这个方法的行为很可能就无法预测了。

但是加锁和解锁都是要消耗一定性能的,因此我们不太可能为所有的方法都加上锁。另外其实在 一个app中可能会涉及到多线程的部分是有限的,我们也没有必要为所有东西加上锁。过多的锁不仅没有意义,而且对于多线程编程来说,可能会产生很多像死锁这样的陷阱,也难以调试。因 此在使用多线程时,我们应该尽量将保持简单作为第一要务。

扯远了,我们回到 @synchronized 上来。虽然这个方法很简单好用,但是很不幸的是在Swift中它已经(或者是暂时)不存在了。其实@synchronized在幕后做的事情是调用了 objc_sync 中的 objc_sync_enterobjc_sync_exit 方法,并且加入了一些异常判断。因此,在Swift中,如果我们忽略掉那些异常的话,我们想要 lock 一个变量的话,可以这样写:

func MyMethod(anObj: Any!) {
    objc_sync_enter(anObj)

    // 在 enter 和 exit 之间 anObj 不会被其他线程改变
    objc_sync_exit(anObj)
}

更进一步,如果我们喜欢以前的那种形式,甚至可以写一个全局的方法,并接受一个闭包,来将objc_sync_enterobjc_sync_exit封装起来:

func synchronized(_ lock: Any, closure:()->()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

再结合 Swift 的尾随闭包的语言特性, 这样, 使用起来就和 Objective-C 中很像了:

func myMethodLocked(anObj: Any!) {
    synchronized(anObj) { 
        // 在 括号内 anObj 不会被其他线程改变
    }
}

results matching ""

    No results matching ""