Grand Central Dispatch 最全入门手册

Grand Central Dispatch 也就是我们常说的 GCD 是苹果为多核心处理器开发的一套异步调度机制。苹果官方文档中是这样介绍它的:

Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in iOS and OS X.

这里比较重要的一点是它依托了语言特性,绝大部分是 Block 特性,使得欲执行的函数体能很方便地作为参数传递。

很多刚接触 iOS 开发的朋友可能会认为 GCD 很复杂,事实上这套 API 十分简洁易用,往往使用之后就不会再去用传统的方式处理多线程问题了,熟练使用 GCD 能极大提升我们的开发效率。下面我将从功能的角度逐一讲解它的使用方法。

<h2>注意,下面的这些函数参数都很明确,我就不一一列举函数的参数是什么了。</h2>

Queue

GCD 的异步核心是列队,系统会有自己的主列队和全局列队,任何在主列队进行的任务都会阻塞 UI 事件,我们只要了解这一点就可以了。

下面是获取和创建列队的函数:

  • dispatch_get_main_queue: 获取主列队。
  • dispatch_get_global_queue: 获取全局列队,其中第一个参数为优先级,可选的优先级有以下几个:
    • QOS_CLASS_USER_INTERACTIVE
    • QOS_CLASS_USER_INITIATED
    • QOS_CLASS_DEFAULT
    • QOS_CLASS_UTILITY
    • QOS_CLASS_BACKGROUND
      从上到下优先级依次降低,该函数的第二个参数为系统保留,每次都传入 0 即可。通常我们第一个参数也传入 0 来获取默认的全局列队。
  • dispatch_queue_create: 创建一个列队,第一个参数为标识符,通常用倒过来写的域名 (eg. com.apple.myqueue) ,第二个参数为列队类型,有下列类型可选:
    • DISPATCH_QUEUE_SERIAL
    • DISPATCH_QUEUE_CONCURRENT
      顾名思义,一个是串行的,一个是并行的。如果传入 NULL则表示串行列队。

有了列队,我们就可以向其中分发任务了,下面是分发任务时需要的函数:

  • dispatch_async: 将执行体放入列队然后立即返回,如果列队是主列队,那么就相当于延后执行代码。
  • dispatch_sync: 将执行体放入列队并执行,等待执行完毕再返回。在主线程中,列队坚决不能是主列队,不然主线程被挂起来等待任务执行结束,然而这个任务就是要在主线程执行,那么这个任务永远不会执行,主线程也永远不会被唤醒。同理列队也不能是调用 dispatch_sync 的当前列队,同样会产生死锁。
  • dispatch_apply: 迭代执行,第一个参数是迭代次数,第二个参数是目标列队,第三个参数是欲执行的 block,block 的参数就是当前迭代的下标。注意,这个函数将会等待所有执行体完毕后才返回。适当情况下可以用这个函数来代替 for-loop 以提高性能。

通常,我们在 Global Queue 中执行一些耗时操作,然后在 Main Queue 中将结果更新到 UI。

Time

GCD 有几个比较重要的函数与时间相关:

  • dispatch_time: 创建一个时间对象,第一个参数是参考时间,第二个参数是时间增量,那么最终返回的时间就是 (参考时间 + 增量),通常使用方法有下面几种:
    • dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_MSEC * 100)): 当前时间后 100 毫秒
    • dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC * 3)): 当前时间后 3 秒
      其中 NSEC_PER_MSECNSEC_PER_SEC 是时间计量单位。
  • dispatch_after: 在指定时间后执行,这个函数将立即返回,等到指定时间后才会在指定的列队中执行指定任务。

Group

如果需要批量执行一批任务,并且想在所有这批任务结束后得到通知,这时就需要用到 Group 了。

  • dispatch_group_create: 创建一个组,无任何参数。
  • dispatch_group_async: 同 dispatch_async ,只不过第一个参数前多了一个 group ,即将任务归为这一组。
  • dispatch_group_enter: 增加组的计数,此时你不需要提交执行体,等待 dispatch_group_leave 被调用后则表示一个任务完成,可嵌套,总之保证两者调用次数平衡即可。下面是一个例子:
let group = dispatch_group_create()

dispatch_group_enter(group)
dispatch_group_enter(group)
dispatch_group_leave(group)
dispatch_group_leave(group)
  • dispatch_group_notify: 在该组任务都完成后在指定列队中调用一个回调函数。
  • dispatch_group_wait: 阻塞线程直到该组任务都完成,或指定时间超时后返回。

Barrier

这个词,有意思。


看翻译感觉很难理解,所以我用通俗的语言表述一下。通常我们会遇到这样一个问题:一个线程需要访问资源 A,而另外一个线程需要修改资源 B,这时就会有竞态问题。我们坚决不能让这个情况出现,这里我们就可以用 Barrier 的这个函数:

dispatch_barrier_async

执行这个函数后,执行体将会被放到列队中并等待,直到整个列队中没有在执行的任务,这时这个执行体才会被执行,并且在它完成之前不会有其他任务会同时进行。简单说就是这个执行体必须自己一个人执行,不得有人和它一同执行。

说起来还是好抽象,上代码:

import Foundation

class Counter {
    
    var counter: Int = 0
    let queue = dispatch_queue_create("com.example.barrierqueue", nil)
    
    func get() -> Int {
        var result: Int!
        dispatch_sync(queue) { 
            result = self.counter
        }
        return result
    }
    
    func enter() {
        adjustBy(1)
    }
    
    func leave() {
        adjustBy(-1)
    }
    
    private func adjustBy(by: Int) {
        dispatch_barrier_async(queue) { 
            self.counter += by
        }
    }
    
}

我们可以看到,访问一个变量可以是同时进行的,因为他们不会修改这个变量。然而进行修改时必须一个一个来,这里我们就可以用 barrier 挡一下,等修改完了再干别的。

Semaphore

信号量是一个控制资源访问数量的方法,例如一个资源只能被 5 个人同时访问,那么我们就可以用信号量记录访问数,并阻挡超出数量的人进行的访问。主要函数有下面几个:

  • dispatch_semaphore_create: 传入一个整型作为最大访问数量,创建并返回这个信号量类型。
  • dispatch_semaphore_wait: 使用资源前调用它,它会减少计数器,如果计数器小于零,这个函数将会阻塞。
  • dispatch_semaphore_signal: 使用资源后调用它,它会增加计数器,如果计数器大于等于零,那些等待使用资源的线程会被唤醒。

其实很多情况我们都用信号量来将一些无法修改的异步函数变成同步函数,下面是一个例子:

func fetchURLSync(URL: NSURL) -> NSData? {
    let sem = dispatch_semaphore_create(0)
    
    var _data: NSData?
    let task = NSURLSession.sharedSession().dataTaskWithURL(URL) { (data, response, error) in
        _data = data
        
        dispatch_semaphore_signal(sem)
    }
    
    task.resume()
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)
    
    return _data
}

Source

用过 CFRunloop / NSRunloop 的朋友应该不陌生这个词,GCD也有 Source 的概念,怎么理解呢,Source 就像一个信号发射器,有信号发出就会有响应的处理。比较常见的信号类型有 Timer文件读写进程UNIX SignalMach Port内存压力。但是在 iOS 开发中,我们也就是用用 Timer 了,下面直接上一个例子:

func createAndStartTimer(interval: UInt64, leeway: UInt64, queue: dispatch_queue_t, block: () -> Void) -> dispatch_source_t? {
    let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
    
    if timer != nil {
        dispatch_source_set_timer(timer, dispatch_walltime(nil, 0), interval, leeway)
        dispatch_source_set_event_handler(timer, block)
        dispatch_resume(timer)
    }
    
    return timer
}

func stopTimer(timer: dispatch_source_t) {
    dispatch_source_cancel(timer)
}

上面代码拿来用就好,leeway 是精准度的意思,精准度越高资源消耗越大。

洋洋洒洒写了这么多,应该算覆盖得比较全了,就酱。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容