GCD

GCD 的整体认识

GCD (grand central dispatch)直译过来是宏大的中心分配,实际上就是一个线程管理技术。这种技术区别于程序员自己手动创建和管理线程,它可以自动根据系统的情况按需创建线程和管理线程。这里所说的系统的情况包括系统运行所在的处理器是多少核的,也就是说,这个GCD技术可以均衡任务到多核上,发挥处理器的多核性能。
GCD的便利在于,它将上述的复杂功能封装出简单易用的接口,要使用该接口只需要理解两个概念,一个是任务,一个是队列,也就是说,只要我们把需要完成的任务添加到队列中,GCD自动帮我们创建线程以执行任务。
GCD中任务可以用两种形式来表示,一种是函数,另一种是block。对应的类型分别是dispatch_function_t和dispatch_block_t。针对这两种任务,操作这两种任务的方法通常会相差一个f,例如添加block形式任务的方法是dispatch_async,那么添加函数形式任务的方法就是dispatch_async_f。
GCD中的队列总是遵循先进先出的原则,也就是先加到队列的任务会先被开始处理。而队列氛围两种,一种是并行的一种是串行的。串行队列就是只会让队列中的任务按照添加顺序一个接一个地执行。并行队列会为任务创建许多线程然后并行执行它们,因此,在这种情况下,即使执行任务的先后仍然遵循先进先出原则,但是由于任务开始后是同一时间在执行,而且每个任务耗时也不同,最终任务的结束时间是无法确定的。
了解两个概念以后,剩下的就是区使用GCD的接口,新建队列,新建任务,将任务添加到队列。

关于新建队列

实际上GCD提供了两种方式来获取队列,一种是名副其实的新建队列,调用队列的创建方法。另一种则是使用GCD预先定义的队列。

1、先说GCD预先定义的队列。

其一,主队列,也就是应用程序主线程所在的队列,这个队列随应用程序的启动就被创建并且利用主线程来处理有关UI的任务。这个队列是个串行队列。其二,GCD自动为应用程序创建的3个并行队列,这三个队列都是一样的,而且是全局的,用队列的优先级来区分。使用这三个队列的便利是不需要自己去retain和release它们。
相应的获取方法:
主线程所在队列:dispatch_queue_t dispatch_get_main_queue(void);
全局的并行队列:dispatch_queue_t dispatch_get_global_queue( long identifier, unsigned long flags);
这里第一个参数就是填优先级,这个优先级用服务质量(QOS)表示也可以用队列优先级(dispatch_queue_priority_t)表示,有以下可选值。它们之间是一一对应的关系,这里的值是按优先级由大到小排序的。优先级的影响是,添加到优先级高的队列的任务,会比其他加入到优先级低的队列的任务要更优先执行。
QOS:
QOS_CLASS_USER_INTERACTIVE,
QOS_CLASS_USER_INITIATED,
QOS_CLASS_UTILITY,
QOS_CLASS_BACKGROUND
dispatch_queue_priority_t:
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
第二个参数是保留参数,填0。


2、再说新建队列

基本的方法就是
dispatch_queue_t dispatch_queue_create( const char *label ,dispatch_queue_attr_t attr);
第一个参数就是一个标识,第二个参数是用来指定该队列的一些属性,可以直接用DISPATCH_QUEUE_SERIAL或DISPATCH_QUEUE_CONCURRENT来指定该对类为串行队列还是并行队列。也可以创建一个dispatch_queue_attr_t类型,这个类型不单可以表明该队列是串行还是并行,还可以指定优先级。创建dispatch_queue_attr_t的方法是dispatch_queue_attr_t dispatch_queue_attr_make_with_qos_class (dispatch_queue_attr_t attr, int qos_class, int relative_priority);
这里第一个参数仍旧是填DISPATCH_QUEUE_SERIAL或DISPATCH_QUEUE_CONCURRENT,第二个参数就是填前面提到的QOS,第三个参数是一个相对于第二个参数中指定的QOS的一个偏移量,是一个负值,作用就是为了进一步细化这个优先级。

3、与队列相关的一些额外的方法

获取当前队列:
dispatch_get_current_queue 
获取某队列的标识:
dispatch_queue_get_label
将一个任务从原队列转移到另一个队列:
void dispatch_set_target_queue( dispatch_object_t object, dispatch_queue_t queue);

关于添加任务到队列

首先来个方法一览,如下:

dispatch_async
dispatch_async_f

dispatch_sync_f
dispatch_after_f 

dispatch_apply_f
dispatch_once
dispatch_once_f 



从这里也可以看到刚才提到的,没有f的方法的操作对象是block形式的任务,有f的方法的操作对象是函数形式的任务。那么,除了操作对象的区别,带不带f的方法功能是一样的,下面就只记录不带f的方法的用法。
dispatch_async,异步添加任务,调用这个方法将任务添加到队列以后就会返回。
dispatch_sync,同步添加任务,调用这个方法将任务添加到队列会等到任务执行完毕后才会返回,因此会造成阻塞。
dispatch_after,在某个时间之后将任务添加到队列,方法中有一个有关时间的参数,类型是dispatch_time_t。之后讲。
dispatch_apply ,将一个任务添加到队列,并执行多次。这个方法的完整定义是void dispatch_apply( size_t iterations, dispatch_queue_t queue, void (^block)( size_t));
其中,第一个参数是执行次数,第三个参数就是以block形式表示的任务,值得提的是那个block中类型为size_t的参数,GCD通过这个参数向block传入执行的次数,在block中可以充分地利用它。
dispatch_once ,将任务添加到队列并且保证该任务只被执行一次,之后不会被再次执行,这个方法常常会使用在单例的初始化方法中。该方法第一个参数是一个dispatch_once_t类型的指针。这个指针只要定义不需要初始化,就可以使用。


关于任务分组

GCD还提供了任务的分组功能。给任务分组,可以对同一个分组的任务进行批量操作。
方法一览:


dispatch_group_async 
dispatch_group_leave 
dispatch_group_notify
dispatch_group_notify_f 

dispatch_group_wait 

dispatch_group_async ,在把任务添加到队列的时候给这个任务指定一个分组
dispatch_group_create,创建分组的方法
dispatch_group_enter 在用非dispatch_group_async方法以外的方法添加任务到某个分组以后,需要显示地更新一下这个分组中的任务数量。enter方法是让分组的任务数加一,对应的leave 方法是让分组的任务数减一。
dispatch_group_leave
dispatch_group_notify ,监视某个分组的任务,当该分组中所有任务的执行完毕后,才将指定的某一个任务添加到指定的队列中。
dispatch_group_wait ,等待指定分组的任务全部执行完,或者到达了预设时间后返回。如果执行完后返回,该函数返回0。

关于有限资源的访问控制

我们知道在我们自己管理线程的时候,可以通过加锁来限制某段代码同一时间只能被一个线程访问。那么这里所说的控制有限资源的访问与加锁的功能概念上有些类似但使用起来又有明显的不同。在GCD中,关于有限资源的访问控制,是指有多段待执行的代码,需要限制同一时间可以执行的代码块不可以超过若干段。
这就如同在商场有好多人等着上厕所,但是该厕所只有五个隔间,因此就限制了同一时间只有5个人使用该厕所,那其他人就得排队,直到里面有人出来,另一个人才能进去。实现这个功能的技术叫做dispatch semaphore,直译过来是分配信号灯。使用的步骤与上述商场厕所的情况非常类似,首先新建一个dispatch semaphore对象,并指定同一时间可使用它的代码段的数目。接着,规定在需要参与访问控制的代码段前调用dispatch_semaphore_wait,在代码段后调用dispatch_semaphore_signal。这么做的作用在于,每当程序运行到dispatch_semaphore_wait方法时就表示已经有一段参与访问控制的代码在执行,那么当前可启动的代码段数目减一;每当运行到dispatch_semaphore_signal方法时,就表示有一段参与访问控制的代码已经执行完毕,那么当前可启动的代码段的数目加一。dispatch semaphore就可以根据当前可启动的代码段的数目来控制这些参与访问控制的代码段的执行,当当前可启动的代码段数目为0时,就不允许新的代码段启动,这样就保证了同时执行的代码段的数目。

关于路障

可以在并行队列中添加一个作为路障的任务,作为路障的任务需要等待比它早添加到队列的任务全部执行完毕之后才会开始执行,且等该路障任务单独执行完毕之后,该并行队列又恢复正常运行。
添加路障的方法一览:

dispatch_barrier_async 

dispatch_barrier_async_f
dispatch_barrier_sync_f 



这几个方法之前的区别和前面提到过的大同小异,也就是操作对象的区别,和同步和异步的区别,这里不多说。

关于事件监视

GCD提供了一套接口——dispatch sources,用来实现,监视某些事件,一旦事件发生就将相应的任务添加到队列中。GCD对连续发生的事件采用了合并策略,也就是说,如果某一事件连续发生,当上一次发生导致任务添加到队列,而该任务尚未被执行的情况下,事件再次发生不会导致新任务添加到队列。也就是说事件本次发生和上次发生进行了合并。
使用这个dispatch sources接口第一步是新建一个dispatch_source_t对象,并指定要监听的事件类型。第二步是根据要监听的事件类型对这个dispatch_source_t对象进行配置。第三步是指定事件发生时需要调用的任务(block)。第四步是开始监听。第五步、取消监听。通常会在事件触发的任务的最后取消监听。下面是对应的方法:
第一步、创建被监听的事件(源)dispatch_source_create
第三步、为某个事件指定发生后需要执行的任务 dispatch_source_set_event_handler
第四步、开始监听事件 dispatch_resume
第五步、取消监听 dispatch_source_cancel
上面提到的事件类型,有以下六大类:timer 、signal、descriptor、process、mach port、custom。具体细分是有以下11种:

#define DISPATCH_SOURCE_TYPE_DATA_ADD 
#define DISPATCH_SOURCE_TYPE_DATA_OR 
#define DISPATCH_SOURCE_TYPE_MACH_RECV 
#define DISPATCH_SOURCE_TYPE_MACH_SEND 
#define DISPATCH_SOURCE_TYPE_PROC 进程相关事件
#define DISPATCH_SOURCE_TYPE_READ 数据可读
#define DISPATCH_SOURCE_TYPE_SIGNAL unix信号到达
#define DISPATCH_SOURCE_TYPE_TIMER 时钟信号到达
#define DISPATCH_SOURCE_TYPE_VNODE 
#define DISPATCH_SOURCE_TYPE_WRITE 数据可写
#define DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 

在第一步创建dispatch_source_t对象的时候,调用的dispatch_source_create方法有四个参数,第一个参数是就是事件类型,第二个参数叫handle,第三个参数叫mask,这两个参数的意义和用途会根据事件类型的不同而不同,具体要参照文档。第四个参数就是事件会触发的任务将要添加到的队列。

关于内存管理

GCD中所有的对象都需要进行手动内存管理。因此在新建以后记得release掉,要使用就记得retain,否则如果引用计数为零,对象就会被销毁。
例外:
三个GCD定义的全局队列以及主线程所在队列不需要进行手动内存管理。任何针对它们的内存管理操作都会被忽略。

你可能感兴趣的:(ios开发学习笔记)