iOS GCD使用指南
Grand Central Dispatch(GCD)是异步运行任务的技术之中的一个。
一般将应用程序中记述的线程管理用的代码在系统级中实现。开发人员仅仅须要定义想运行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划运行任务。因为线程管理是作为系统的一部分来实现的,因此可统一管理。也可运行任务,这样就比曾经的线程更有效率。
Dispatch Queue是用来运行任务的队列,是GCD中最主要的元素之中的一个。
Dispatch Queue分为两种:
letmyQueue: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自己主动管理,假设是在此之前的版本号,须要自己手动释放,例如以下:
letmyQueue:dispatch_queue_t=dispatch_queue_create("com.xxx",nil)
dispatch_async(myQueue, { () -> Voidin
println("in Block")
})
dispatch_release(myQueue)
以上是通过手动创建的方式来获取Dispatch Queue。另外一种方式是直接获取系统提供的Dispatch Queue。
要获取的Dispatch Queue无非就是两种类型:
//获取Main Dispatch Queue
letmainQueue =dispatch_get_main_queue()
//获取Global Dispatch Queue
letglobalQueue =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能让我们加入进队列的任务延时运行,比方想让一个Block在10秒后运行:
vartime =dispatch_time(DISPATCH_TIME_NOW, (Int64)(10*NSEC_PER_SEC))
dispatch_after(time, globalQueue) { () -> Voidin
println("在10秒后运行")
}
NSEC_PER_SEC表示的是秒数。它还提供了NSEC_PER_MSEC表示毫秒。
获取一个dispatch_time_t类型的值能够通过两种方式来获取,以上是第一种方式,即通过dispatch_time函数。还有一种是通过dispatch_walltime函数来获取,dispatch_walltime须要使用一个timespec的结构体来得到dispatch_time_t。
通常dispatch_time用于计算相对时间,dispatch_walltime用于计算绝对时间,我写了一个把NSDate转成dispatch_time_t的Swift方法:
funcgetDispatchTimeByDate(date:NSDate) ->dispatch_time_t{
letinterval = date.timeIntervalSince1970
varsecond =0.0
letsubsecond =modf(interval, &second)
vartime =timespec(tv_sec:__darwin_time_t(second), tv_nsec: (Int)(subsecond * (Double)(NSEC_PER_SEC)))
returndispatch_walltime(&time,0)
}
这种方法接收一个NSDate对象,然后把NSDate转成dispatch_walltime须要的timespec结构体。最后再把dispatch_time_t返回,相同是在10秒后运行。之前的代码在调用部分须要改动成:
vartime=getDispatchTimeByDate(NSDate(timeIntervalSinceNow:10))
dispatch_after(time, globalQueue) { () -> Voidin
println("在10秒后运行")
}
这就是通过绝对时间来使用dispatch_after的样例。这个时候就须要使用dispatch_group了:
letglobalQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
letgroup =dispatch_group_create()
dispatch_group_async(group, globalQueue) { () -> Voidin
println("1")
}
dispatch_group_async(group, globalQueue) { () -> Voidin
println("2")
}
dispatch_group_async(group, globalQueue) { () -> Voidin
println("3")
}
dispatch_group_notify(group, globalQueue) { () -> Voidin
println("completed")
}
输出的顺序与加入进队列的顺序无关。因为队列是Concurrent Dispatch Queue,但“completed”的输出一定是在最后的:312 completed除了使用dispatch_group_notify函数能够得到最后运行完的通知外。还能够使用
letglobalQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
letgroup =dispatch_group_create()
dispatch_group_async(group, globalQueue) { () -> Voidin
println("1")
}
dispatch_group_async(group, globalQueue) { () -> Voidin
println("2")
}
dispatch_group_async(group, globalQueue) { () -> Voidin
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就如同它的名字一样,在队列运行的任务中添加“栅栏”。在添加“栅栏”之前已经開始运行的block将会继续运行。当dispatch_barrier_async開始运行的时候其它的block处于等待状态,dispatch_barrier_async的任务运行完后。其后的block才会运行。我们简单的写个样例。假设这个样例有读文件和写文件的部分:
funcwriteFile() {
NSUserDefaults.standardUserDefaults().setInteger(7, forKey:"Integer_Key")
}
funcreadFile(){
print(NSUserDefaults.standardUserDefaults().integerForKey("Integer_Key"))
}
写文件仅仅是在NSUserDefaults写入一个数字7。读仅仅是将这个数字打印出来而已。我们要避免在写文件时候正好有线程来读取,就使用dispatch_barrier_async函数:NSUserDefaults.standardUserDefaults().setInteger(9, forKey:"Integer_Key")
letglobalQueue =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运行完后再运行。letglobalQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
dispatch_apply(10, globalQueue) { (index) -> Voidin
print(index)
}
print("completed")
因为是Concurrent Dispatch Queue,不能保证哪个索引的元素是先运行的。可是“completed”一定是在最后打印。因为dispatch_apply函数是同步的。运行过程中会使线程在此处等待。所以一般的,我们应该在一个异步线程里使用dispatch_apply函数:letglobalQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
dispatch_async(globalQueue, { () -> Voidin
dispatch_apply(10, globalQueue) { (index) -> Voidin
print(index)
}
print("completed")
})
print("在dispatch_apply之前")
//暂停
dispatch_suspend(globalQueue)
//恢复
dispatch_resume(globalQueue)
暂停时。假设已经有block正在运行。那么不会对该block的运行产生影响。dispatch_suspend仅仅会对还未開始运行的block产生影响。信号量的详细做法是:当信号计数大于0时,每条进来的线程使计数减1,直到变为0。变为0后其它的线程将进不来。处于等待状态;运行完任务的线程释放信号,使计数加1。如此循环下去。以下这个样例中使用了10条线程,可是同一时候仅仅运行一条,其它的线程处于等待状态:
letglobalQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
letsemaphore =dispatch_semaphore_create(1)
foriin0...9{
dispatch_async(globalQueue, { () -> Voidin
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER)
lettime=dispatch_time(DISPATCH_TIME_NOW, (Int64)(2*NSEC_PER_SEC))
dispatch_after(time, globalQueue) { () -> Voidin
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自己主动管理,假设是在此之前的版本号,须要自己手动释放。
classSingletonObject {
classvarsharedInstance :SingletonObject{
structStatic {
staticvaronceToken :dispatch_once_t=0
staticvarinstance :SingletonObject? =nil
}
dispatch_once(&Static.onceToken) {
Static.instance =SingletonObject()
}
returnStatic.instance!
}
}
这样就能通过GCD的安全机制保证这段代码仅仅运行一次。