iOS开发之再探多线程编程:Grand Central Dispatch详解

Swift3.0相关代码已在github上更新。以前关于iOS开发多线程的内容发布过一篇博客,其中介绍了NSThread、操做队列以及GCD,介绍的不够深刻。今天就以GCD为主题来全面的总结一下GCD的使用方式。GCD的历史以及好处在此就不作过多的赘述了。本篇博客会经过一系列的实例来好好的总结一下GCD。GCD在iOS开发中仍是比较重要的,使用场景也是很是多的,处理一些比较耗时的任务时基本上都会使用到GCD, 在使用是咱们也要主要一些线程安全也死锁的东西。git

本篇博客中对iOS中的GCD技术进行了较为全面的总结,下方模拟器的截图就是咱们今天要介绍的内容,都是关于GCD的。下方视图控制器中每点击一个Button都会使用GCD的相关技术来执行不一样的内容。本篇博客会对使用到的每一个技术点进行详细的讲解。在讲解时,为了易于理解,咱们还会给出原理图,这些原理图都是根据本篇博客中的实例进行创做的,在其余地方可见不着。github

  

上面每一个按钮都对应着一坨坨的代码,上面这个截图算是咱们本篇博客的一个,下面咱们将会对每坨代码进行详细的介绍。经过这些介绍,你应该对GCD有了更全面并且更详细的了解。建议参考着下方的介绍,而后本身动手去便实现代码,这样效果是灰常的好的。本篇博客中的全部代码都会在github上进行分享,本篇博客的后方会给出github分享地址。其实本篇博客能够做为你的GCD参考手册,虽然本篇博客没有囊括全部GCD的东西,可是平时常用的部分仍是有的。废话少说,进入今天博客的主题,接下来咱们将一个Button接着一个Button的介绍。安全

 

1、经常使用GCD方法的封装网络

为了便于实例的实现,咱们首先对一些经常使用的GCD方法进行封装和提取。该部分算是为下方的具体实例作准备的,本部分封装了一些下面示例所公用的方法。接下来咱们将逐步的对每一个提取的函数进行介绍,为下方示例的实现作准备。在封装方法以前,要说明一点的是在GCD中咱们的任务是放在队列中在不一样的线程中执行的,要明白一点就是咱们的任务是放在队列中的Block中,而后Block再在相应的线程中完成咱们的任务。多线程

以下图所示,在下方队列中存入了三个Block,每一个Block对应着一个任务,这些任务会根据队列的特性已经执行方式被放到相应的线程中来执行。队列可分为并行队列(Concurrent Qeueu)串行队列(Serial Queue),队列能够进行同步执行(Synchronize)以及异步执行(Asynchronize), 稍后会进行详细的分析与介绍。咱们要知道队列第GCD的基础。闭包

  

 

1.获取当前线程与当前线程休眠app

首先咱们将获取当前线程的方法进行封装,由于有时候咱们会常常查看咱们的任务是在那些线程中执行的。在此咱们使用了NSThread的currentThread()方法来获取当前线程。下方的getCurrentThread()方法就是咱们提取的获取当前线程的方法。方法内容比较简单,在此就不作过多赘述了。异步

  

上述代码段是获取当前线程的方法,接着咱们要实现一个让当前线程休眠的方法。由于咱们在示例时,经常会让当前线程来休眠一段时间来模拟那些耗时的操做。下方代码段中的currentThreadSleep()函数就是咱们提取的当前线程休眠的函数。该函数有一个NSTimeInterval类型的参数,该参数就是要休眠的时间。NSTimeInterval其实就是Double类型的别名,因此咱们在调用currentThreadSleep()方法时须要传入一个Double类型的休眠时间。固然你也能够调用sleep()方法来对当前线程进行休眠,可是须要注意的是sleep()的参数是UInt32位的整型。下方就是咱们休眠当前线程的函数。async

  

 

2.获取主队列与全局队列函数

下方封装的getMainQueue()函数就是获取主队列的函数,由于有时候咱们在其余线程中处理完耗时的任务(好比网络请求)后,须要在主队列中对UI进行更新。由于咱们知道在iOS中有个RunLoop的概念,在iOS系统中触摸事件、屏幕刷新等都是在RunLoop中作的。由于本篇博客的主题是GCD, 在此就对RunLoop作过多的赘述了,若是你对RunLoop不太了解,那么你就先简单将RunLoop理解成1/60执行一次的循环便可,固然真正的RunLoop要比一个单纯的循环复杂的多,之后有机会的话在以RunLoop为主题更新一篇博客吧。言归正传,下方就是获取咱们主队列的方法,简单一点的说由于咱们要更新UI,因此要获取主队列。

  

接下来咱们要封装一个获取全局队列(Global Queue)的函数,在封装函数以前咱们先来聊聊什么是全局队列。全局队列是系统提供的一个队列,该队列拿过来就能用,按执行方式来讲,全局队列应该称得上是并行队列,关于串并行队列的具体概念下方会给出介绍。咱们在获取全局队列的时候要知道其队列的优先级,优先级越高的队列就越先执行,固然该处的优先级不是绝对的。队列真正的执行顺序还须要根据CUP当前的状态来定,大部分是按照你指定的队列优先级来执行的,不过也有例外。下方实例会给出详细的介绍。下方就是咱们获取全局队列的函数,在获取全局队列为为全局队列指定一个优先级,默认为DISPATCH_QUEUE_PRIORITY_DEFAULT

  

 

3.建立串行队列与并行队列

由于咱们在实现实例时会建立一些并行队列和串行队列,因此咱们要对并行队列的建立于串行队列的建立进行提取。GCD中是调用dispatch_queue_create()函数来建立咱们想要的线程的。dispatch_queue_create()函数有两个参数,第一个参数是队列的标示,用来标示你建立的队列对象,通常是域名的倒写如“cn.zeluli”这种形式。第二个参数是所建立队列的类型,DISPATCH_QUEUE_CONCURRENT就说明建立的是并行队列,DISPATCH_QUEUE_SERIAL代表你建立的是串行队列。至于二者的区别,仍是那句话,下方实例中会给出详细的介绍。

  

 

 

2、同步执行与异步执行

同步执行可分为串行队列的同步执行和并行队列的同步执行,而异步执行又可分为串行队列的异步执行和并行队列的异步执行。也许听起来有些拗口,不经过下方的图解你会很好的理解这一律念。上一部分算是咱们的准备工做,接下来才是咱们真正的主题。在第一部分咱们实现了获取当前线程,对当前线程休眠,获取主队列和全局队列以及建立并行队列和串行队列。在本部分将要利用上述函数来进一步讨论串行队列与并行队列的同步执行以及串行队列与并行队列的异步执行。而且会给出同步执行与异步执行的区别。

在聊同步执行与异步执行以前咱们先聊聊串行队列(Serial Queue)与并行队列(Concurrent Queue)的区别。不管是Serial Queue仍是Concurrent Queue,都是队列,只要是队列都遵循FIFO(First In First Out -- 先入先出)的规则,排队嘛,固然是谁先来的谁先走了。不过在Serial Queue中要等到前面的任务出队列并执行完后,下一个任务才能出队列进行执行。而Concurrent Queue则否则,只要是队列前面的任务出队列了,而且还有有空余线程,无论前面的任务是否执行完了,下一任务均可以进行出队列。

关于串行队列和并行队列的问题,咱们能够拿银行办业务排队来类比一下。好比你如今在串行队列中排的是1号窗口,你必须等前面一我的在1号窗口办完业务你才能够去1号窗口中去办你的业务,就算其余窗口空着你也不能去,由于你选择的是串行队列。可是若是你是在并行队列中的话,只要你前面的人去窗口中办业务了,此时你无需关系你前面的人的业务是否办完了,只要有其余窗口空着你就能够去办理你的业务。总结一下:串行队列就是认准一个线程,一条道走到黑,比较专一;并行队列就是能利用其余线程就利用,比较灵活,不钻牛角尖。接下来咱们要看一下两个队列的不一样执行方法。

1.同步执行

首先咱们先来介绍同步执行,关于同步执行的主要实例对应着“同步执行串行队列”和“同步执行并行队列”这两个按钮。Serial Queue能够同步执行,Concurrent Queue亦能够同步执行。咱们先抛开队列,看一下同步执行的代码如何。下方的函数就是对同步执行的任务进行封装。同步执行就是使用dispatch_sync()方法进行执行。在下方函数中经过for-in循环以同步执行的方式往queue(队列)中添加了3个Block执行块。函数的参数是队列类型(dispatch_queue_t),能够给该函数传入串行队列和并行队列。

  

 

也就是说要同步执行串行队列就给函数传入串行队列的对象,若是要同步执行并行队列就传入并行队列对象。此时咱们就用到了以前封装的建立串行队列和并行队列的方法(参见第一部分)。下方代码段就是点击“同步执行串行队列”和“同步执行并行队列”这两个按钮所作的事情。点击“同步执行串行队列”按钮时就建立一个串行队列的对象传给上面同步执行的函数(performQueuesUseSynchronization()),点击“同步执行并行队列”按钮时就建立一个并行队列的对象给上面的函数。

  

 

下方截图是点击两个按钮所运行的结果。红框中是同步执行串行队列的结果,能够看出来是在当前线程(主线程)下按着FIFO的顺序来执行的。而绿框中的是同步执行并行队列的运行结果,从结果中部门不难看出,与红框中的结果一致,也是在当前线程中按着FIFO的顺序来执行的。

  

经过上面两种不一样队列的同步执行方式咱们给出了下面的分析图。Serial Queue与Concurrent Queue中都有4个Block(编号为1--4),而后使用dispatch_sync()来同步执行。由上述示例咱们能够得出,同步执行方式,也就是使用dispatch_sync()来执行队列不会开辟新的线程,会在当前线程中执行任务。若是当前线程是主线程的话,那么就会阻塞主线程,由于主线程被阻塞了,就会会形成UI卡死的现象。由于同步执行是在当前线程中来执行的任务,也就是说如今能够供队列使用的线程只有一个,因此串行队列与并行队列使用同步执行的结果是同样的,都必须等到上一个任务出队列并执行完毕后才能够去执行下一个任务。咱们可使用同步执行的这个特色来为一些代码块加同步锁。下方就是上面代码以及执行结果的描述图。

   

 

二、异步执行

接下来咱们看异步执行,一样异步执行也分为串行队列的异步执行并行队列的异步执行。在GCD中使用dispatch_async()函数来进行异步执行,dispatch_async()函数的参数与dispatch_sync()函数的参数一致。只不过是dispatch_async()异步执行不会在当前线程中执行,它会开辟新的线程因此异步执行不会阻塞当前线程。下方代码段就是咱们封装的异步执行的函数,其中主要是对dispatch_async()函数的使用。下方为了让队列中的Block的三个输出语句顺序输出,咱们将其放在了一个同步队列中来执行,从而这三个输出语句能够顺序执行。

  

 

(1)、串行队列的异步执行

有了上面的函数后,咱们就能够给上面的函数传入Serial Queue队列的对象,从而观察串行队列异步执行结果。对应这咱们第一张截图中的“异步执行串行队列”的按钮,下方是点击该按钮执行的方法。在该按钮点击的方法中咱们调用了performQueuesUseAsynchronization()方法,而且传入了一个串行队列。也就是串行队列的异步执行。

  

点击按钮就会执行上述方法,下方是点击按钮后,也就是“异步执行串行队列”时在控制台中输出的结果。从输出结果中咱们不难看出,异步执行并无阻塞当前线程。使用dispatch_saync()开辟了新的线程(线程的number = 3)来执行Block中的内容。而Block内容外的东西依然在以前的线程(在该示例中是main_thread)中进行执行。从下方的结果中来分析,就是for循环执行完毕后主线程的任务就结束了,至于Block中的内容就交给新开辟的线程3来执行了。

  

 

根据上面的输出结果,咱们能够画出下方异步执行串行队列的分析图。在线程1中的一个串行队列若是使用异步执行的话,会开辟一个新的线程2来执行队列中的Block任务。在新开辟的线程中依然是FIFO, 而且执行顺序是等待上一个任务执行完毕后才开始执行下一个任务。以下所示。

   

(2)、并行队列的异步执行

接下来来讨论一下并行队列的异步执行方式。其实并行队列与异步执行方式相结合才能大大的提供效率,由于使用异步执行并行队列时会开辟多个线程来同时执行并行队列中的任务。好比如今开辟了10个线程,那么异步队列会根据FIFO的顺序出来10个任务,这10个任务会进入到不一样的线程中来执行,至于每一个任务执行完的前后顺序由每一个任务的复杂度而定。异步队列的特色是只要有可用的线程,任务就会出队列进行执行,而不关心以前出队列的任务(Block)是否执行完毕。下方的方法就是点击“异步执行并行队列”按钮所调用的方法。该方法会调用performQueuesUseAsynchronization()函数,并传入一个并行队列的对象。

  

点击按钮就会执行上述方法,并行队列就会异步执行。下方结果就是并行队列异步执行后输出的结果,解析来让咱们来分析一下输出结果。下方第一个红框中是并行队列中任务的顺序,由前到后为0、一、2,紧接着是每一个任务执行后所输出的结果。从任务执行完打印结果咱们能够看出,执行完成的顺序是二、一、0,每一个任务都会在一个新的线程中来执行的。若是你在点击一下按钮,执行完成的顺序有多是二、0、1等其余的顺序,因此并行队列异步执行中每一个任务结束时间有主要由任务自己的复杂度而定的。

  

 根据上面的执行结果,咱们画出了下方的解说图。当并行队列异步执行时会开辟多个新的线程来执行队列中的任务,队列中的任务出队列的顺序仍然是FIFO,只不过是不须要等到前面的任务执行完而已,只要是有空余线程可使用就能够按FIFO的顺序出队列进行执行。 

  

 

 

3、延迟执行

在GCD中咱们使用dispatch_after()函数来延迟执行队列中的任务, dispatch_after()是异步执行队列中的任务的,也就是说使用dispatch_after()来执行队列中的任务不会阻塞当前任务。等到延迟时间到了之后就会开辟一个新的线程而后执行队列中的任务。要注意一点是延迟时间到了后再开辟新的线程而后当即执行队列中的任务。下方是dispatch_after()函数的使用方式。

在下方代码中使用了两种方式来建立延迟时间,一个是使用dispatch_time()来建立延迟时间,另外一个是使用dispatch_walltime()来建立时间。前者是取的是当前设备的时间,后者去的是挂钟的时间,也就是绝对时间,若是设备休眠了那么前者也就休眠了,然后者是是根据挂钟时间不会有当前设备的状态而左右的。下面在建立dispatch_time_t对象的时候,有一个参数是NSEC_PER_SEC,从命名只能怪咱们就能够知道NSEC_PER_SEC表示什么意思,就是每秒包含多少纳秒。你能够将该值进行打印,发现NSEC_PER_SEC = 1_000_000_000。也就是一秒等于10亿纳秒。若是下方的time不乘于NSEC_PER_SEC那么就表明1纳秒的时间,也就是说此处的时间是按纳秒(nanosecond)来计算的。下方就是延迟执行的的代码,由于改代码输出结果比较简单,在此就不作过多的赘述了。须要注意的是延迟执行会在新开辟的队列中进行执行,不会阻塞新的线程。

  

 

4、队列的优先级

队列也是有优先级的,但其优先级不是绝对的大部分状况由于XUN内核用于GCD不是实时性的,优先级只是大体的来判断队列的执行优先级。队列分为四个优先级,由高到底分别是High > Default > Low > Background。上面在获取全局队列时咱们能够为获取的队列指定优先级,而且可使用dispatch_set_target_queue()函数将一个队列的优先级赋值给另外一个队列。下方咱们先给全局队列指定优先级,而后在将其赋值给其余队列。

1.为全局队列指定优先级

本部分对应着“设置全局队列的优先级”这个button,点击该button就会获取4个不一样优先级的全局队列,而后异步进行全局队列的执行,最后观察执行的结果。下方就是点击该按钮所要执行的函数。我先获取了四种不一样优先级的全局队列,而后进行异步执行,并打印执行结果。

  

 

上述代码的运行结果以下,虽然在上述代码中优先级高的代码放在了最后来进行异步执行,但是却先被打印了。打印的顺序是Hight->Default->Low->Background,这个打印顺序就是其执行顺序,从打印顺序中咱们不难看出优先级高的先被执行。固然这不是绝对的。

   

 

2. 为自建立的队列指定优先级

在GCD中你可使用dispatch_set_target_queue()函数为你本身建立的队列指定优先级,这个过程还需借助咱们的全局队列。下方的代码段中咱们先建立了一个串行队列,而后经过dispatch_set_target_queue()函数将全局队列中的高优先级赋值给咱们刚建立的这个串行队列,以下所示。

  

 

5、任务组dispatch_group

GCD的任务组在开发中是常常被使用到,当你一组任务结束后再执行一些操做时,使用任务组在合适不过了。dispatch_group的职责就是当队列中的全部任务都执行完毕后在去作一些操做,也就是说在任务组中执行的队列,当队列中的全部任务都执行完毕后就会发出一个通知来告诉用户任务组中所执行的队列中的任务执行完毕了。关于将队列放到任务组中执行有两种方式,一种是使用dispatch_group_async()函数,将队列与任务组进行关联并自动执行队列中的任务。另外一种方式是手动的将队列与组进行关联而后使用异步将队列进行执行,也就是dispatch_group_enter()与dispatch_group_leave()方法的使用。下方就给出详细的介绍。

1.队列与组自动关联并执行

首先咱们来介绍dispatch_group_async()函数的使用方式,该函数会将队列与相应的任务组进行关联,而且自动执行。当与任务组关联的队列中的任务都执行完毕后,会经过dispatch_group_notify()函数发出通知告诉用户任务组中的全部任务都执行完毕了。使用通知的方式是不会阻塞当前线程的,若是你使用dispatch_group_wait()函数,那么就会阻塞当前线程,直到任务组中的全部任务都执行完毕

下方封装的函数就是使用dispatch_group_async()函数将队列与任务组进行关联并执行。首先咱们建立了一个concurrentQueue并行队列,而后又建立了一个类型为dispatch_group_t的任务组group。使用dispatch_group_async()函数将二者进行关联并执行。使用dispatch_group_notify()函数进行监听group中队列的执行结果,若是执行完毕后,咱们就在主线程中对结果进行处理。dispatch_group_notify()函数有两个参数一个是发送通知的group,另外一个是处理返回结果的队列。

  

 

调用上述函数的输出结果以下。从输出结果中咱们不难看出,队列中任务的执行以及通知结果的处理都是异步执行的,不会阻塞当前的线程。在任务组中全部任务都处理完毕后,就会在主线程中执行dispatch_group_notify()中的闭包块。

  

 

2. 手动关联队列与任务组

接下来咱们将手动的管理任务组与队列中的关系,也就是不使用dispatch_group_async()函数。咱们使用dispatch_group_enter()与dispatch_group_leave()函数将队列中的每次任务加入到到任务组中。首先咱们使用dispatch_group_enter()函数进入到任务组中,而后异步执行队列中的任务,最后使用dispatch_group_leave()函数离开任务组便可。下面的函数中咱们使用了dispatch_group_wait()函数,该函数的职责就是阻塞当前线程,来等待任务组中的任务执行完毕。该函数的第一个参数是所要等待的group,第二个参数是等待超时时间,此处咱们设置的是DISPATCH_TIME_FOREVER,就说明等待任务组的执行永不超时,直到任务组中全部任务执行完毕。

  

下方是上述函数执行后的输出结果,dispatch_group_wait()函数下方的print()函数在全部任务执行完毕以前是不会被调用的,由于dispatch_group_wait()会将当前线程进行阻塞。固然虽然是手动的将队列与任务组进行关联的,display_group_notify()函数仍是好用的。运行结果以下所示。

  

 

6、信号量(semaphore)同步锁

有时候多个线程对一个数据进行操做的时候,为了数据的一致性,咱们只容许一次只有一个线程来操做这个数据。为了保证一次只有一个线程来修改咱们的资源数据,咱们就得用到信号量同步锁了。也就是说一个存放资源房间的门后边又把锁,当有一个线程进到这个房间后就将这把锁锁上。当这个线程修改完该资源后,就将锁给打开,锁打开后其余的线程就能够持有资源了。若是你上了锁不打卡,而其余线程等待使用该资源时,就会产生死锁。因此当你不使用的时候,就不要持有资源呢。

上述这个过程,在GCD中咱们可使用信号量机制来完成。在GCD中有一个叫dispatch_semaphore_t的东西,这个就是咱们的信号量。咱们能够对信号量进行操做,若是信号量为0那么就是上锁的状态,其余线程想使用资源就得等待了。若是信号量不为零,那么就是开锁状态,开锁状态下资源就能够访问。下方代码就是信号量的具体使用代码。

下方第一个红框中就是经过dispatch_semaphore_create()来建立信号量,该函数须要一个参数,该参数所指定的就是信号量的值,咱们为信号指定的值为1。第二个红框中是“上锁的过程”,经过dispatch_semaphore_wait()函数对信号量操做,该函数中的第一个参数是所操做的信号量,第二个参数是等待时间。dispatch_semaphore_wait()函数是对信号量减一,信号量为零的话就对当前线程所操做的资源加锁。其余线程等待当前线程操做资源的时间为DISPATCH_TIME_FOREVER,也就是说其余线程要一直等下去,等待当前线程操做资源完毕。当当前线程对资源操做完毕后调用dispatch_semaphore_signal()将信号量加1,将资源进行解锁,以便于其余等待的线程进行资源的访问。当解锁后,其余线程等待的时间结束,就能够进行资源的访问了。

  

 

7、队列的循环、挂起、恢复

在本篇博客的第七部分,咱们要聊一下队列的循环执行以及队列的挂起与恢复。该部分比较简单,可是也是比较经常使用的。在重复执行队列中的任务时,咱们一般使用dispatch_apply()函数,该函数循环执行队列中的任务,可是dispatch_apply()函数自己会阻塞当前线程。若是你使用dispatch_apply()函数来执行并行队列,虽然会开启多个线程来循环执行并行队列中的任务,可是仍然会阻塞当前线程。若是你使用dispatch_apply()函数来执行串行队列的话,那么就不会开辟新的线程,固然就会将当前线程进行阻塞。说到队列的挂起与恢复你可使用dispatch_suspend()来挂起队列,使用dispatch_resum()来恢复队列。请看下方实例。

一、dispatch_apply()函数

dispatch_apply()函数是用来循环来执行队列中的任务的,使用方式为:dispatch_apply(循环次数, 任务所在的队列) { 要循环执行的任务 }。使用该函数循环执行并行队列中的任务时,会开辟新的线程,不过有可能会在当前线程中执行一些任务。而使用dispatch_apply()执行串行队列中的任务时,会在当前线程中执行。不管是使用并行队列仍是串行队列,dispatch_apply()都会阻塞当前线程。下方代码段就是dispatch_apply()的使用示例:

  

下方则是上述函数的运行结果。在结果中咱们将每次执行任务所使用的线程进行了打印。

  

 

2. 队列的挂起与唤醒

队列的挂起与唤醒相对较为简单,若是你想对一个队列中的任务的执行进行挂起,那么你就使用dispatch_suspend()函数便可。若是你要唤醒某个挂起的队列,那么你就可使用dispatch_resum()函数。这两个函数所需的参数都是你要挂起或者唤醒的队列,鉴于知识点的简单性就不作过多的赘述了。下方是对异步执行的并行队列进行挂起,在当前线程休眠2秒后唤醒被挂起的线程。具体代码以下:

  

 

8、任务栅栏dispatch_barrier_async()

顾名思义,任务栅栏就是将队列中的任务进行隔离的,是任务能分拨的进行异步执行。我想用下方的图来介绍一下barrier的做用。咱们假设下方是并行队列,而后并行队列中有1.一、1.二、2.一、2.2四个任务,前两个任务与后两个任务本中间的栅栏给隔开了。若是没有中间的栅栏的话,四个任务会在异步的状况下同时执行。可是有栅栏的拦着的话,会先执行栅栏前面的任务。等前面的任务都执行完毕了,会执行栅栏自带的Block ,最后异步执行栅栏后方的任务。这么一说有点与前面的dispatch_group相似,当执行完一些列的任务后,咱们想作一些事情的话,咱们也可经过dispatch_barrier_async()来实现。

  

下方代码段就是咱们dispatch_barrier_async(), 具体的使用方式。上面的红色框中的代码是异步执行的第一批任务,中间是咱们给任务队列添加的任务栅栏,dispatch_barrier_asyn()的一个参数就是栅栏所在的队列,然后边的尾随闭包就是在栅栏前面的全部任务都执行完毕后就会执行该尾随闭包中的内容。而最下方黄色框中的部分就是第二批次执行的任务,该批任务会在dispatch_barrier_asyn()栅栏的尾随闭包执行后会继续执行。

  

接下来咱们来看一下上述代码的运行结果,点击咱们第一部分截图的“使用任务隔离栅栏”按钮就会执行上述方法。下方就是上述代码片断的运行结果。从下面的输出结果中不难看出,dispatch_barrier_asyn以前的任务会先异步执行,也就是下方的第一批任务。第一批任务完成后,会在第一批任务中的最后完成任务的线程中来执行栅栏中的任务块。当栅栏中的任务执行完毕后,队列中的第二批任务中的第一个会进入执行栅栏任务的线程中来执行,其余的会开辟新的线程。以下所示。

  

咱们能够用一个图来结合上述示例来解释栅栏的工做方式。下图画的就是栅栏工做的方式,须要注意的是队列中的第一批任务中的最后一个任务与栅栏中的任务已经第二批第一个任务是用一个线程来执行的。这就是为何栅栏能进行任务隔离的根本了。从下方的图中咱们不难发现,任务1.三、栅栏任务、任务2.1在线程5中是同步执行的。具体请看下图。

  

9、dispatch_source

dispatch_source在GCD中是一个比较灵活的东西,功能也是很是强大的。简单的说,dispatch_source的主要功能就是对某些类型事件的对象进行监听,当事件发生时将要处理的事件放到关联的队列中进行执行。dispatch源支持事件的取消,咱们也能够对取消事件的进行处理。下方是dispatch源的不一样类型,由于篇幅有限在此就不作过多的赘述了,关于这些类型的资料网上一抓一大把。今天就以DATA_ADD, DATA_OR, TIMER为例,看一下source的使用方式。

  

 

1. DATA_ADD 与DATA_OR

DISPATCH_SOURCE_TYPE_DATA_ADDDISPATCH_SOURCE_TYPE_DATA_OR用法差很少一个是将数据源进行相加,一个是进行或操做。咱们就以相加的为例,或操做的代码在博客中就不给出了,不过咱们github上分享的代码会有完整的示例。下方函数是DISPATCH_SOURCE_TYPE_DATA_ADD类型的dispatch源的使用。

首先咱们获取了一个全局队列queue,而后建立了一个dispatch源,命名为dispatchSource。在建立dispatch源时,咱们为dispatch源指定了类型,而且为其关联的一个queue队列。关联这个队列的做用是用来处理dispatch源中的事件的。而后咱们使用dispatch_source_set_event_handler()为咱们的source指定处理事件。该事件会在必定的条件下回触发,至于触发的条件有dispatch源的类型锁定。由于此处咱们dispatch源的类型是DISPATCH_SOURCE_TYPE_DATA_ADD,因此使用dispatch_source_merge_data()就能够触发上面咱们指定的事件。由于dispatch源建立后是处于挂起的状态,因此咱们须要使用dispatch_resume()对咱们建立的事件源进行恢复。恢复后的dispatch源才能够监听条件从而触发事件。

下方代码段在for循环中调用dispatch_source_merge_data()方法。在执行过程当中咱们还能够调用dispatch_source_cancel()对dispatch源进行取消。当dispatch source被取消后,就会执行咱们所设置取消dispatch_source要处理的事件。咱们经过dispatch_source_set_candel_handel()来指定取消dispatch source要执行的事件。关于dispatch_source的取消,咱们会在下面倒计时的时候给出。

咱们此处建立的dispatch_source的类型是Data Add类型,也就是说当咱们指定的源事件未处理完时,那么下一个Data就要进行等待。而等待的数据会经过dispatch_source_merge_data()方法进行合并。若是你建立的是DISPATCH_SOURCE_TYPE_DATA_ADD类型的dispatch_source,那么就会按照加法进行合并。若是你是建立的DISPATCH_SOURCE_TYPE_DATA_OR类型的dispatch_source, 那么就会经过或运算进行合并。合并在一块儿的数据会一同触发咱们设定的事件。

  

上述代码段就是对DATA_ADD类的的dispatch源进行的测试。咱们定义了一个变量sum来模拟数据的合并,而后观察每次合并的数据与咱们自定的sum中计算的数据是否相同。合并后每次执行一次事件咱们都将sum进行归零,而后进行下一轮的合并。下方就是上述代码输出的结果。从下方的结果中咱们能够看出,在上述的10次循环中执行了四次咱们指定的source事件,并且每次执行事件所merge的Data与咱们手动记录的sum一致。这就是DATA_ADD的工做方式,运行效果以下所示。关于Data_Or的运行方式在此就不作过多的赘述了。

  

 

2.定时器

在GCD的dispatch源中还有定时器类型,咱们能够建立定时器类型的dispatch源,而后经过dispatch_source_set_event_handler()来设定源事件。而后经过dispatch_source_set_timer()函数来为定时器类型的dispatch_source指定时间间隔,该函数第一个参数就是dispatch source,第二个参数就是触发事件的时间间隔,第三个参数就是容许偏差的时间。当咱们设定的倒计时的次数到是,咱们就调用dispatch_source_cancel()来进行dispatch_source的取消,当取消后就会执行dispatch_source_set_cancel_handel()方法中的尾随闭包。

下方示例是使用DISPATCH_SOURCE_TYPE_TIMER类型的dispatch source进行的10秒到计时,等咱们设置的事件执行10次后咱们就取消dispatch_source。对于下方的示例来讲,当dispatch source经过dispatch_resume()函数进行唤醒后,会开始倒计时。会在倒计时10秒后结束计时。

  

 

下方就是上述倒计时代码所执行后的结果。从运行结果中咱们不难看出,当倒计时开始时,会新开辟一些新的线程来顺序执行倒计时任务。尽管你使用的是并行队列,虽然每次开辟的线程可能会不一样,可是都会顺序的执行倒计时任务,

  

 

今天博客的内容也够多的了,应该说还算是干活满满,上述全部代码将会在github上进行分享,下方是分享地址。有什么问题,或者须要补充的加QQ群吧,以前的群人已经满了,加不进去了,就建立了一个新的。

github分享地址为:https://github.com/lizelu/GCDDemo-Swift

相关文章
相关标签/搜索