Swift之GCD使用指南1
Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue
中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
Dispatch Queue
Dispatch Queue
是用来执行任务的队列,是GCD中最基本的元素之一。
Dispatch Queue
分为两种:
- Serial Dispatch Queue,按添加进队列的顺序(先进先出)一个接一个的执行
- Concurrent Dispatch Queue,并发执行队列里的任务
简而言之,Serial Dispatch Queue只使用了一个线程,Concurrent Dispatch Queue使用了多个线程(具体使用了多少个,由系统决定)。 可以通过两种方式来获得Dispatch Queue,第一种方式是自己创建一个:
let myQueue: dispatch_queue_t = dispatch_queue_create("com.xxx", nil)
第一个参数是队列的名称,一般是使用倒序的全域名。虽然可以不给队列指定一个名称,但是有名称的队列可以让我们在遇到问题时更好调试;当第二个参数为nil时返回Serial Dispatch Queue,如上面那个例子,当指定为DISPATCH_QUEUE_CONCURRENT
时返回Concurrent Dispatch Queue
。
需要注意一点,如果是在OS X 10.8
或iOS 6
以及之后版本中使用,Dispatch Queue
将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放,如下:
let myQueue: dispatch_queue_t = dispatch_queue_create("com.xxx", nil)
dispatch_async(myQueue, { () -> Void in
println("in Block")
})
dispatch_release(myQueue)
以上是通过手动创建的方式来获取Dispatch Queue
,第二种方式是直接获取系统提供的Dispatch Queue
。要获取的Dispatch Queue
无非就是两种类型:
- Main Dispatch Queue
- Global Dispatch Queue / Concurrent Dispatch Queue
一般只在需要更新UI时我们才获取Main Dispatch Queue
,其他情况下用Global Dispatch Queue
就满足需求了:
/获取Main Dispatch Queue
let mainQueue = dispatch_get_main_queue()
//获取Global Dispatch Queue
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
得到的Global Dispatch Queue
实际上是一个Concurrent Dispatch Queue
,Main Dispatch Queue
实际上就是Serial Dispatch Queue
(并且只有一个)。获取Global Dispatch Queue
的时候可以指定优先级,可以根据自己的实际情况来决定使用哪种优先级。一般情况下,我们通过第二种方式获取Dispatch
就行了。
Queue
dispatch_after
dispatch_after
能让我们添加进队列的任务延时执行,比如想让一个Block在10秒后执行:
var time = dispatch_time(DISPATCH_TIME_NOW, (Int64)(10 * NSEC_PER_SEC))
dispatch_after(time, globalQueue) { () -> Void in
println("在10秒后执行")
}
NSEC_PER_SEC
表示的是秒数,它还提供了NSEC_PER_MSEC
表示毫秒。
上面这句dispatch_after
的真正含义是在10秒后把任务添加进队列中,并不是表示在10秒后执行,大部分情况该函数能达到我们的预期,只有在对时间要求非常精准的情况下才可能会出现问题。
获取一个dispatch_time_t类型的值可以通过两种方式来获取,以上是第一种方式,即通过dispatch_time
函数,另一种是通过dispatch_walltime
函数来获取,dispatch_walltime
需要使用一个timespec
的结构体来得到dispatch_time_t
。通常dispatch_time
用于计算相对时间,dispatch_walltime
用于计算绝对时间,我写了一个把NSDate转成dispatch_time_t的Swift方法:
func getDispatchTimeByDate(date: NSDate) -> dispatch_time_t {
let interval = date.timeIntervalSince1970
var second = 0.0
let subsecond = modf(interval, &second)
var time = timespec(tv_sec: __darwin_time_t(second), tv_nsec: (Int)(subsecond * (Double)(NSEC_PER_SEC)))
return dispatch_walltime(&time, 0)
}
这个方法接收一个NSDate对象,然后把NSDate转成dispatch_walltime需要的timespec结构体,最后再把dispatch_time_t返回,同样是在10秒后执行,之前的代码在调用部分需要修改成:
var time = getDispatchTimeByDate(NSDate(timeIntervalSinceNow: 10))
dispatch_after(time, globalQueue) { () -> Void in
println("在10秒后执行")
}
这就是通过绝对时间来使用dispatch_after的例子。
dispatch_group
可能经常会有这样一种情况:我们现在有3个Block要执行,我们不在乎它们执行的顺序,我们只希望在这3个Block执行完之后再执行某个操作。这个时候就需要使用dispatch_group了:
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let group = dispatch_group_create()
dispatch_group_async(group, globalQueue) { () -> Void in
println("1")
}
dispatch_group_async(group, globalQueue) { () -> Void in
println("2")
}
dispatch_group_async(group, globalQueue) { () -> Void in
println("3")
}
dispatch_group_notify(group, globalQueue) { () -> Void in
println("completed")
}
输出的顺序与添加进队列的顺序无关,因为队列是Concurrent Dispatch Queue
,但“completed”的输出一定是在最后的:
312
completed
除了使用dispatch_group_notify
函数可以得到最后执行完的通知外,还可以使用
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let group = dispatch_group_create()
dispatch_group_async(group, globalQueue) { () -> Void in
println("1")
}
dispatch_group_async(group, globalQueue) { () -> Void in
println("2")
}
dispatch_group_async(group, globalQueue) { () -> Void in
println("3")
}
//使用dispatch_group_wait函数
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
println("completed")
需要注意的是,dispatch_group_wait实际上会使当前的线程处于等待的状态,也就是说如果是在主线程执行dispatch_group_wait,在上面的Block执行完之前,主线程会处于卡死的状态。可以注意到dispatch_group_wait的第二个参数是指定超时的时间,如果指定为DISPATCH_TIME_FOREVER(如上面这个例子)则表示会永久等待,直到上面的Block全部执行完,除此之外,还可以指定为具体的等待时间,根据dispatch_group_wait的返回值来判断是上面block执行完了还是等待超时了。最后,同之前创建dispatch_queue一样,如果是在OS
X 10.8或iOS 6以及之后版本中使用,Dispatch Group将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放。
dispatch_barrier_async
dispatch_barrier_async
就如同它的名字一样,在队列执行的任务中增加“栅栏”,在增加“栅栏”之前已经开始执行的block将会继续执行,当dispatch_barrier_async开始执行的时候其他的block处于等待状态,dispatch_barrier_async的任务执行完后,其后的block才会执行。我们简单的写个例子,假设这个例子有读文件和写文件的部分:
func writeFile() {
NSUserDefaults.standardUserDefaults().setInteger(7, forKey: "Integer_Key")
}
func readFile(){
print(NSUserDefaults.standardUserDefaults().integerForKey("Integer_Key"))
}
写文件只是在NSUserDefaults写入一个数字7,读只是将这个数字打印出来而已。我们要避免在写文件时候正好有线程来读取,就使用dispatch_barrier_async函数:
SUserDefaults.standardUserDefaults().setInteger(9, forKey: "Integer_Key")
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_barrier_async(globalQueue) {self.writeFile() ; self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
我们先将一个9初始化到NSUserDefaults
的Integer_Key
中,然后在中间执行dispatch_barrier_async
函数,由于这个队列是一个Concurrent Dispatch Queue
,能同时并发多少线程是由系统决定的,如果添加dispatch_barrier_async
的时候,其他的block(包括上面4个block)还没有开始执行,那么会先执行dispatch_barrier_async
里的任务,其他block全部处于等待状态。如果添加dispatch_barrier_async
的时候,已经有block在执行了,那么dispatch_barrier_async
会等这些block执行完后再执行。
dispatch_apply
dispatch_apply
会将一个指定的block执行指定的次数。如果要对某个数组中的所有元素执行同样的block的时候,这个函数就显得很有用了,用法很简单,指定执行的次数以及Dispatch Queue
,在block回调中会带一个索引,然后就可以根据这个索引来判断当前是对哪个元素进行操作:
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_apply(10, globalQueue) { (index) -> Void in
print(index)
}
print("completed")
由于是Concurrent Dispatch Queue,不能保证哪个索引的元素是先执行的,但是“completed”一定是在最后打印,因为dispatch_apply函数是同步的,执行过程中会使线程在此处等待,所以一般的,我们应该在一个异步线程里使用dispatch_apply函数:
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(globalQueue, { () -> Void in
dispatch_apply(10, globalQueue) { (index) -> Void in
print(index)
}
print("completed")
})
print("在dispatch_apply之前")
dispatch_suspend / dispatch_resume
某些情况下,我们可能会想让Dispatch Queue
暂时停止一下,然后在某个时刻恢复处理,这时就可以使用dispatch_suspend
以及dispatch_resume
函数:
/
/暂停
dispatch_suspend(globalQueue)
//恢复
dispatch_resume(globalQueue)
暂停时,如果已经有block正在执行,那么不会对该block的执行产生影响。dispatch_suspend
只会对还未开始执行的block产生影响。
Dispatch Semaphore
信号量在多线程开发中被广泛使用,当一个线程在进入一段关键代码之前,线程必须获取一个信号量,一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待前面的线程释放信号量。
信号量的具体做法是:当信号计数大于0时,每条进来的线程使计数减1,直到变为0,变为0后其他的线程将进不来,处于等待状态;执行完任务的线程释放信号,使计数加1,如此循环下去。下面这个例子中使用了10条线程,但是同时只执行一条,其他的线程处于等待状态:
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let semaphore = dispatch_semaphore_create(1)
for i in 0 ... 9 {
dispatch_async(globalQueue, { () -> Void in
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
let time = dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC))
dispatch_after(time, globalQueue) { () -> Void in
print("2秒后执行")
dispatch_semaphore_signal(semaphore)
}
})
}
取得信号量的线程在2秒后释放了信息量,相当于是每2秒执行一次。
通过上面的例子可以看到,在GCD中,用dispatch_semaphore_create函数能初始化一个信号量,同时需要指定信号量的初始值;使用dispatch_semaphore_wait函数分配信号量并使计数减1,为0时处于等待状态;使用dispatch_semaphore_signal函数释放信号量,并使计数加1。
另外dispatch_semaphore_wait同样也支持超时,只需要给其第二个参数指定超时的时候即可,同Dispatch Group的dispatch_group_wait函数类似,可以通过返回值来判断。
这个函数也需要注意,如果是在OS X 10.8或iOS 6以及之后版本中使用,Dispatch Semaphore将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放。
dispatch_once
dispatch_once
函数通常用在单例模式上,它可以保证在程序运行期间某段代码只执行一次,如果我们要通过dispatch_once
创建一个单例类,在Swift可以这样:
class SingletonObject {
class var sharedInstance : SingletonObject {
struct Static {
static var onceToken : dispatch_once_t = 0
static var instance : SingletonObject? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = SingletonObject()
}
return Static.instance!
}
}
这样就能通过GCD的安全机制保证这段代码只执行一次。
Swift之GCD使用指南1的更多相关文章
- Swift之GCD 使用指南2
Grand Central Dispatch大中枢派发:joy: 或俗称 GCD 是一件极其强大的武器.它为你提供了很多底层工具(比如队列和信号量),你可以组合这些工具来实现自己想要的多线程效果.不幸 ...
- swift GCD使用指南
swift GCD使用指南 Grand Central Dispatch(GCD)是异步执行任务的技术之一.一般将应用程序中记述的线程管理用的代码在系统级中实现.开发者只需要定义想执行的任务并追加到适 ...
- Swift中GCD与NSOperation相关
GCD Swift 3必看:从使用场景了解GCD新API 常用写法: dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_ ...
- iOS开发 - Swift使用GCD实现计时器功能
前言 开发中,经常会用到定时执行网络请求.倒计时.计时器等功能,本篇文章介绍在iOS开发中,Swift怎样使用GCD实现这些功能. 执行一次 下面的代码将会在5秒后执行,且只执行一次. let tim ...
- iOS GCD使用指南
Grand Central Dispatch(GCD)是异步运行任务的技术之中的一个. 一般将应用程序中记述的线程管理用的代码在系统级中实现.开发人员仅仅须要定义想运行的任务并追加到适当的Dispat ...
- iOS开发:Swift多线程GCD的使用
除了上一篇文章说到到NSThread线程,还有一个GCD(Grand Central Dispath),是Apple新开发的一个解决多核编程的解决方案,充分的利用CPU资源,将所有的任务,放到一个任务 ...
- The Swift Programming Language--语言指南--协议
Protocol(协议)用于统一方法和属性的名称,而不实现任何功能.协议能够被类,枚举,结构体实现,满足协议要求的类,枚举,结构体被称为协议的遵循者. 遵循者需要提供协议指定的成员,如属性,方法, ...
- iOS开发之Swift 4 JSON 解析指南
Apple 终于在 Swift 4 的 Foundation 的模块中添加了对 JSON 解析的原生支持. 虽然已经有很多第三方类库实现了 JSON 解析,但是能够看到这样一个功能强大.易于使用的官方 ...
- Swift 中函数使用指南
关于Swift中的各种函数的使用的总结 前言 时间久了,好多东西我们就会慢慢忘记,在这里总结一下Swift中函数的使用原则,把大部分的函数使用技巧用代码示例来做了演示,但是如果想提高,还是要多多思考才 ...
随机推荐
- ROS(indigo)RRT路径规划
源码地址:https://github.com/nalin1096/path_planning 路径规划 使用ROS实现了基于RRT路径规划算法. 发行版 - indigo 算法在有一个障碍的环境找到 ...
- Android快速关联V4包的方式
很多时候需要管理v4包,当然有很多种办法去关联.本人觉得最快速的方式,是通过添加配置文件的方式.只需要ctrl+c和ctrll+v就能解决了 方法如下: 1.新建一个android-support-v ...
- Unity3D核心技术详解
在这里将多年游戏研发经验的积累写成一本书奉献给读者,目前已经开始预售,网址: http://www.broadview.com.cn/article/70 该书主要是将游戏中经常使用的技术给大家做了一 ...
- ListView嵌套GridView
首先,我们通过两个实例来了解下本篇文章所讲的重点,看下图: 微博: 陌陌: 大家应该对这两款软件并不陌生,接下来,我将列举下本文将要实现的几个点: 1.ListView嵌套GridView,互不冲突, ...
- Windows安装和使用fftw
FFTW是一个比较快的.非常出名的一个DFT的开源库. 本文探索安装和配置FFTW,用Visual Studio 2008来使用fftw. 第一步:下载最新的fftw库 这一步很简单,只要在googl ...
- 04_关于元数据,ResultSetMetaData对象以及API方法介绍
ResultSetMetaData对象 元数据,可以理解为数据的数据 Jdbc中的元数据是指数据库.表.列的定义信息. ResultSetMetaData对象表示结果集ResultSet对象的元 ...
- Android开发学习之路--图表实现(achartengine/MPAndroidChart)之初体验
已经有一段时间没有更新博客了,在上周离开工作了4年的公司,从此不再安安稳稳地工作了,更多的是接受挑战和实现自身价值的提高.离开了嵌入式linux,从此拥抱移动互联网,也许有点为时已晚,但是相信通过 ...
- acm入门搜索-石油数目
题意:给出一个N*M的矩形区域和每个区域的状态--有/没有石油,(定义)如果两个有石油的区域是相邻的(水平.垂直.斜)则认为这是属于同一个oil pocket. 求这块矩形区域一共有多少oilpock ...
- iOS中 项目开发易错知识点总结 韩俊强的博客
每日更新关注:http://weibo.com/hanjunqiang 新浪微博! 点击return取消textView 的响应者 - (BOOL)textFieldShouldReturn:(UI ...
- Nginx模块之SessionSticky
0 工作原理 Session Sticky 模块在upstream 返回响应后,向客户的浏览器写入 Cookie ,默认名为route ,保存的内容是一个 md5 码. 之后,模块接收到客户浏览器的请 ...