【iOS高级编程学习笔记】GCD相关API说明

本文深入解析了Grand Central Dispatch (GCD) 的概念与应用,包括其基本原理、API使用方法及多线程编程技巧。介绍了如何利用GCD进行高效的任务调度与资源管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GCD定义

Grand Central Dispatch(GCD 大中枢派发 ?)是一步执行任务的技术之一。一般将应用程序中技术的线程管理用代码在系统级中实现。开发者只需要定义向执行的任务,并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。

也就是说,GCD用非常简洁的记述方法,实现了极为负责繁琐的多线程编程。


dispatch_async(queue, ^{
    /*
     * 长时间处理
     * 例如数据库访问,AR识别等
     */
    
    /*
     * 长时间处理结束,主线程使用该处理结果。
     */
    
    dispatch_async(dispatch_get_main_queue(), ^{
        /*
         * 只在主线程可以执行的处理,例如更新界面
         */
    })
    
});

复制代码

其中:dispatch_async(queue, ^{表示让处理在后台线程中执行。

dispatch_async(dispatch_get_main_queue(), ^{表示让处理在主线程中执行。

在导入GCD之前,Cocoa框架提供了NSObject类的performSelectorInBackground:<#(nonnull SEL)#> withObject:<#(nullable id)#>实例方法和performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>实例方法等简单的多线程编程技术。

例如,可以用performSelector系列方法来实现前面使用了GCD的代码:

/*
 *  NSObject performSelectorInBackground: withObject: 方法中执行后台线程
 */
- (void) launchThreadByNSObject_performSelectorInBackground_withObject{
    
    [self performSelectorInBackground:@selector(doWork) withObject:nil];
    
}


- (void)doWork{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    /*
     * 长时间处理
     * 例如数据库访问,AR识别等
     */
    
    /*
     * 长时间处理结束,主线程使用该处理结果。
     */
    [self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
    [pool drain];
    
}

/*
 * 主线程处理方法
 */
-(void)doneWork{
    /*
     * 只在主线程可以执行的处理,例如更新界面
     */
}

复制代码

多线程编程

一段源代码通过编译器转换为CPU命令行(二进制代码)。汇集CPU命令行和数据,将其作为一个应用程序安装到Mac或者iPhone上。Mac、iPhone的操作系统OS X、iOS根据用户的指示启动改应用程序后,首先便将包含在应用程序中的CPU命令列配置到内存中。CPU从应用程序指定的地址开始,一个一个地执行CPU命令行。

在OC的if语句和for语句等控制语句或函数调用的情况下,执行命令行的地址会远离当前的位置(位置迁移)。但是,由于一个CPU一次只能执行一个命令,不能执行某处分开的两个并列的命令,因为通过CPU执行的CPU命令列就好比一条无分叉的大道,其执行不回出现分歧。

这里所说的“1个CPU执行的CPU命令列为一条无分叉路径”,即线程。

现在一个物理的CPU芯片实际上有64个(64核)CPU,如果1个CPU核虚拟为两个CPU核工作,那么一台计算机上使用多个CPU核就是理所当然的了。尽管如此,线程的概念仍然不变。

这种无分叉路经不只1条,存在有多条时即多线程。在多线程中,1个CPU核执行多条不同路径上的命令。

虽然CPU技术在不断进步,但基本上1个CPU核一次能够执行的CPU命令始终为1。

OS X和iOS的核心XNU内核在发生操作系统事件时(如每隔一段时间,唤起系统调用等情况)会切换执行路径。执行中路径的状态,例如CPU的寄存器等信息保存到个字路径专用的内存块中,从切换目标路径专用的内存块中复原CPU寄存器等信息,继续执行切换路径的CPU命令列。这被称为“上下文切换”。

由于使用多线程的应用可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列第执行多个线程一样。而且在具有多个CPU核的情况下,就不是看上去像了,而是真正第提供了多个CPU核并行执行多个线程的技术。这种利用多线程编程的技术就被称为多线程编程。

但是,多线程编程实际上是一种易发生各种问题的编程技术。比如多个线程更新相同的资源会导致数据的不一致(数据竞争)、停止等待时间的线程会导致多个线程相互持续等待(死锁)、使用太多线程会消耗大量内存等。

尽管极易发生各种问题,也应当使用多线程编程,因为多线程编程可保证应用程序的响应性问题。

应用程序启动时,通过最先执行的线程,即“主线程”来描绘用户界面、处理触摸屏幕的时间等。如果在该主线程中进行长时间的处理,如AR用画像的识别或者数据库访问,就会妨碍主线程的执行(发生阻塞)。在OS X和iOS的应用程序中,会妨碍主线程中被称为RunLoop的主循环的执行,从而导致不能更新用户界面,应用程序的画面长时间停止等待等问题。

这就是长时间的处理不在主线程中执行而在其他线程中执行的原因。

使用多线程编程,在执行长时间的处理时仍可保成用户界面的相应性能。

GCD的API

1.Dispatch Queue

苹果官方对GCD说明为 开发者要做的只是定义向执行的任务并指甲到适当的Dispatch Queue中。

用代码描述如下:

dispatch_async(queue, ^{
	/*
	 * 想执行的任务
	 */
 
});
复制代码

该源代码使用Block语法“定义想执行的任务”,通过dispatch_async函数“追加”赋值在变量queue中的“Dispatch Queue”中。仅这样就可以指定在Block在另一线程中执行。

Dispatch Queue是执行处理的等待队列。通过dispatch_async函数等API,在Block语法中记述想执行的处理并将其追加到Dispatch Queue中。Dispatch Queue按照追加的顺序(先进先出first in firt out FIFO)执行处理。

另外在执行处理是存在两种Dispatch Queue,一种是等待现在执行中处理的Serail Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue。

Dispatch Queue的种类说明
Serail Dispatch Queue等待现在执行中处理结束
Concurrent Dispatch Queue不等待现在执行中处理结束

Serial Dispatch Queue -- 线程池只提供一个线程用来执行任务,所以后一个任务必须等到前一个任务执行结束才能开始。

Concurrent Dispatch Queue -- 线程池提供多个线程来执行任务,所以可以按序启动多个任务并发执行。

这样虽然不用等待处理结束,可以并行执行多个处理,但并行执行的处理数量取决于当前系统的状态。即iOS和OS X基于Dispatch Queue中的处理数、CPU核数以及CPU负荷等当年系统的状态来决定Concurrent Dispatch Queue中并行执行的处理数。

iOS和OS X的核心---XNU内核决定应当使用的线程数,并只生成所需要的线程执行处理。另外,当处理结束,应当执行的处理数减少时,XNU内核会结束不再需要的线程。XNU内核仅使用Concurrent Dispatch Queue便可以完美滴管理并执行多个处理的线程。

比如:Concurrent Dispatch Queue可以并行处理4个线程。

线程0线程1线程2线程3
blk0blk1blk2blk3
blk4blk6blk5
blk7

像这样在Concurrent Dispatch Queue中执行处理是,执行顺序会根据处理内容和系统状态发生改变。它不同于执行顺序固定的Serail Dispatch Queue。在不能改变执行的处理顺序或并不想并行执行多个处理时使用Serial Dispatch Queue。

2.dispatch_queue_create

通过GCD的dispatch_queue_create函数生成Dispatch Queue。

Serial Dispatch Queue生成代码如下:

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue",NULL);
复制代码

Serial Dispatch Queue生成个数的注意事项:

Concurrent Dispatch Queue并行执行多个追加处理,而Serial Dispatch Queue同时只能执行1个追加处理。虽然Serial Dispatch Queue和Concurrent Dispatch Queue受到系统资源的限制,但用dispatch_queue_create函数可生成任意多个Dispatch Queue。

当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。虽然在1个Serial Dispatch Queue中只能同时执行一个追加处理,但如果将处理分别追加到4个Serial Dispatch Queue中,各个Serial Dispatch Queue执行1个,即为同时执行4个处理。

如果过多使用多线程,就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能。所以只在为了避免多线程编程问题之一,一个多线程更新相同资源导致数据竞争时使用Serial Dispatch Queue。

当想并行执行不发生数据竞争等问题的处理时,使用Concurrent Dispatch Queue。而对于Concurrent Dispatch Queue来说,不管生成多少,由于XNU内核只使用有效管理的线程,因此不会发生Serial Dispatch Queue的那些问题。


dispatch_queue_create函数的第一个参数指定Serial Dispatch Queue的名称,如果以上示例代码,Dispatch Queue 推荐使用应用程序ID这种逆序全程域名(FQDN, fully qualified domain name)。该名称在Xcode和Instruments的调试器中作为Dispatch Queue名称表示。另外,该名称也出现在应用程序崩溃时所产生的CrashLog中。

生成Serial Dispatch Queue时,第二个参数指定为NULL;生成Concurrent Dispatch Queue时,第二个参数指定成DISPATCH_QUEUE_CONCURRENT。

dispatch_queue_create函数的返回值为表示“Dispatch Queue”的dispatch_queue_t类型。

dispatch_queue_t  myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue",DISPATCH_QUEUE_CONCURRENT);

dispatch_async(myConcurrentDispatchQueue, ^{
    NSLog(@"block on myConcurrentDispatchQueue");
});

dispatch_release(myConcurrentDispatchQueue);
复制代码

以上代码在myConcurrentDispatchQueue中执行指定的Block。

另外注意:Dispatch Queue不像Block一样作为OC对象来处理,所以必须手动释放。

dispatch_release(myConcurrentDispatchQueue);
复制代码

相应地,还存在dispatch_retain函数

dispatch_retain(myConcurrentDispatchQueue);
复制代码

即Dispatch Queue 也像引用计数式内存管理一样,需要通过dispatch_retain函数和dispatch_release函数的引用计数来管理内存。

那么如上例通过dispatch_async函数追加Block到Concurrent Dispatch Queue,换言之,该Block通过dispatch_retain函数持有Dispatch Queue。无论类型是Serial Dispatch Queue还是Concurrent Dispatch Queue都一样。一旦Block执行结束,就通过dispatch_release函数释放该Block持有的Dispatch Queue。

也就是说在dispatch_async函数中追加Block到Dispatch Queue后,即使立即释放Dispatch Queue,该Dispatch Queue由于被Block所持有也不会呗废弃,因为Block能够执行。Block执行结束后会释放Dispatch Queue,这时谁都不持有Dispatch Queue,因为它会被废弃。

另外,GCD的API名称中含有“create”的API在生成对象时,有必要通过dispatch_retain函数持有,在不需要其生成的对象时,有必须要通过dispatch_release函数进行释放。


3.Main Dispatch Queue/Global Dispatch Queue

系统标准提供的Dispatch Queue :Main Dispatch Queue/Global Dispatch Queue

Main Dispatch Queue 是主线程执行的Dispatch Queue。因为主线程只有1个,所以Main Dispatch Queue是Serial Dispatch Queue。

追加到Main Dispatch Queue的处理在主线程的RunLoop中进行。由于在主线程中执行,因此要将用户界面的界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue使用。

另一个Global Dispatch Queue是所有应用程序都能够使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create函数逐个生成Concurrent Dispatch Queue。只要获取Global Dispatch Queue使用即可。

Global Dispatch Queue有4个执行优先级,分别是高优先级,默认优先级,低优先级和后台优先级。通过XNU内核管理的用于Global Dispatch Queue的线程,将各自使用的Global Dispatch Queue的执行优先级作为线程的执行优先级使用。在想Global Dispatch Queue追加处理时,应选择与处理内容对应的执行优先级的Global Dispatch Queue。

但是通过XNU内核用于Global Dispatch Queue的线程并不能保证实时性,因此执行优先级只是大概的判断。例如在处理内容的执行可有可无时,使用后台优先级的Global Dispatch Queue等,只能进行这种程度的区分。

Dispatch Queue 的种类

名称Dispatch Queue的种类说明
Main Dispatch QueueSerial Dispatch Queue主线程执行
Global Dispatch Queue(High Priority)Concurrent Dispatch Queue执行优先级:高
Global Dispatch Queue(Default Priority)Concurrent Dispatch Queue执行优先级:默认
Global Dispatch Queue(Low Priority)Concurrent Dispatch Queue执行优先级:低
Global Dispatch Queue(Background Priority)Concurrent Dispatch Queue执行优先级:后台

各种Dispatch Queue的获取方法如下:

//Main Dispatch  Queue的获取方法
    dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
    
    //Global Dispatch Queue(High Priority)的获取方法
    dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
    //Global Dispatch Queue(Default Priority)的获取方法
    dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //Global Dispatch Queue(Low Priority)的获取方法
    dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

    //Global Dispatch Queue(Background Priority)的获取方法
    dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

复制代码

另外,对于Main Dispatch Queue和Global Dispatch Queue执行dispatch_retain函数和dispatch_release函数不会引起任何变化,也不会有任何问题。

/*
 * 在Global Dispatch Queue(Default Priority)中执行Block
 */ 

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     
    /*
     * 可并行执行的处理
     */ 
     
    /*
     * 在Main Dispatch Queue中执行Block
     */     
	dispatch_async(dispatch_get_main_queue(), ^{
	    //只能在主进程中执行的处理
	    
	});

});

复制代码

4.dispatch_set_target_queue

dispatch_queue_create函数生成的Dispatch Queue(Serial/Concurrent Dispatch Queue),都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。而编程生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数。

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue",NULL);
    
//Global Dispatch Queue(Background Priority)的获取方法
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
    
复制代码

指定要变更执行优先级的Dispatch Queue为dispatch_set_target_queue函数的第一个参数,指定与要使用的执行优先级相同优先级的Global Dispatch Queue为第二个参数(目标)。

将Dispatch Queue指定为dispatch_set_target_queue函数的参数,不仅可以变更Dispatch Queue的执行优先级,还可以做成Dispatch Queue的执行阶层。如果在多个Serial Dispatch Queue中执行dispatch_set_target_queue函数指定目标为某一个Serial Dispatch Queue,那么原先本应并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能同时执行一个处理。

在必须将不可并行执行额处理追加到多个Serial Dispatch Queue中,如果使用dispatch_set_target_queue函数将目标指定为某一个Serial Dispatch Queue,即可防止处理并行执行。


5.dispatch_after

在指定时间后执行处理,用dispatch_after函数实现。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);


dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"waited at least three seconds");
    });
复制代码

需要注意的是,dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue。

因为Main Dispatch Queue在主线程RunLoop中执行,所以在比如每隔1/60秒执行的RunLoop中,Block最快在3秒后执行,最慢在3秒+1/60秒后执行,并且在Main Dispatch Queue有大量处理追加或主线程的处理本身有延长时,这个时间会更长。

该函数的第一个参数为dispatch_time_t 类型的值,该类型的值可以用dispatch_time或者diapatch_walltime函数做成。

dispatch_time函数的第一个参数经常使用的值是DISPATCH_TIME_NOW,这表示现在的时间。第二个参数是数值和NSEC_PER_SEC的乘积得到的单位为毫微秒的数值。“ull”是C语言的数值字面量,是现实表明类型时使用的字符串(表示unsigned long long)。如果使用NSEC_PER_MSEC则可以以毫秒为单位计算。

dispatch_walltime函数由POSIX中使用的struct timespec类型的时间得到dispatch_time_t类型的值。它用于计算绝对时间。这可作为粗略的闹钟功能使用。

struct timespec类型的时间可以很轻松地通过NSDate类对象做成。


dispatch_time_t getDispatchTimeByDate(NSDate *date)
{
    NSTimeInterval interval;
    double second,subsecond;
    struct timespec time ;
    dispatch_time_t milestone;
    
    interval = [date timeIntervalSince1970];
    subsecond = modf(interval, &second);
    time.tv_sec = second;
    time.tv_sec = subsecond * NSEC_PER_SEC;
    milestone = dispatch_walltime(&time, 0);
    return milestone;
    
}

复制代码

6.Dispatch Group

在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这种情况会经常出现。只使用一个Serial Dispatch Queue时,只要将想执行的处理全部追加到该Serial Dispatch Queue中并在最后追加结束处理,即可实现。但是在使用Concurrent Dispatch Queue时或同时使用多个Dispatch Queue时,就会变得复杂,这时候使用Dispatch Group。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
dispatch_group_t group  = dispatch_group_create();
    
dispatch_group_async(group, queue, ^{
    NSLog(@"blk0");
});
    
dispatch_group_async(group, queue, ^{
    NSLog(@"blk1");
});
    
dispatch_group_async(group, queue, ^{
    NSLog(@"blk2");
});
    
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"done");
});

复制代码

因为像Global Dispatch Queue即Concurrent Dispatch Queue追加处理,多个线程并行执行,所以追加处理的执行顺序不定。执行时会发生变化,但是此执行结果的done一定是最后输出的。

无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可监视这些处理执行的结束。一旦检测到所有处理执行结束,就可将结束的处理追加到Dispatch Queue中。这就是使用Dispatch Group的原因。

首先dispatch_group_create函数生成dispatch_group_t类型的Dispatch Group。使用结束后需要release。

dispatch_group_async函数与dispatch_async函数相同,都追加Block到指定的Dispatch Queue中。与dispatch_async函数不同的是指定生成的Dispatch Group 为第一个参数,指定的Block属于指定的Dispatch Group。

另外,与追加Block到Dispatch Queue时相同,Block 通过dispatch_retain函数持有Dispatch Group,从而使得该Block属于Dispatch Group。这样如果Block执行结束,该Block就通过dispatch_release函数释放持有的Dispatch Group。一旦Dispatch Group使用结束,不用考虑属于该Dispatch Group的Block,立即通过dispatch_release函数释放即可。

在追加到Dispatch Group中的处理全部执行结束时,该源代码中dispatch_group_notify函数会将执行的Block追加到Dipspatch Queue中,将第一个参数指定为要兼职的Dispatch Group。在追加到该Dispatch Group的全部处理执行结束时,将第三个参数的Block追加到第二个参数的Dispatch Queue中。

另外,在Dispatch Group 中也可以使用dispatch_group_wait函数仅等待全部处理执行结束。

dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
复制代码
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group,time);
if(result == 0){
	//属于Dispatch Group的全部处理执行结束
}else{
	//属于Dispatch Group的某一个处理还在执行中
}
复制代码

如果dispatch_group_wait函数的返回值不为0,就意味着虽然经过了指定的时间,但属于Dispatch Queue的某一个处理还在执行中。如果返回值为0,那么全部处理执行结束。当等待时间为DISPATCH_TIME_FOREVER、由dispatch_group_wait函数返回时,由于属于Dispatch Group的处理必定全部执行结束,因此返回值恒为0。

等待就意味着一旦调用dispatch_group_wait函数,该函数就处于调用的状态而不返回。即执行dispatch_group_wait函数的现在的线程(当前线程)停止。在经过dispatch_group_wait函数中指定的时间或属于指定Dispatch Group的处理全部执行结束前,执行该函数的线程停止。

指定DISPATCH_TIME_NOW,则不用任何等待即可判定属于Dispatch Group的处理是否执行结束。

long reuslt = dispatch_group_wait(group,DISPATCH_TIME_NOW);
复制代码

在主线程的RunLoop的每次循环中,可检查执行是否结束,从而不耗费多余的等待时间,虽然这样也可以,但一般在这种情形下,还是推荐用dispatch_group_notify函数追加结束处理到Main Dispatch Queue中,更简化代码。


7.dispatch_barrier_async

dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中。然后在由dispatch_barrier_async函数追加的处理执行完毕后,Concurrent Dispatch Queue才恢复为一般的动作,追加到该Concurrent Dispatch Queue的处理由开始并执行。

dispatch_async(queue,blk0_for_reading);
dispatch_async(queue,blk1_for_reading);
dispatch_async(queue,blk2_for_reading);
dispatch_async(queue,blk3_for_reading);
dispatch_barrier_async(queue,blk_for_writing);
dispatch_async(queue,blk4_for_reading);
dispatch_async(queue,blk5_for_reading);
dispatch_async(queue,blk6_for_reading);
dispatch_async(queue,blk7_for_reading);
复制代码

使用Concurrent Dispatch Queue和dispatch_barrier_async函数可实现高效率的数据库访问和文件访问。


8.dispatch_sync

dispatch_async是异步执行,就是将指定的Block非同步地追加到指定的Dispatch Queue中,不做任何等待。

dispatch_sync意味着将指定的Block同步追加到指定的Dispatch Queue中,在追加Block结束之前,dispatch_sync函数会一直等待。

调用dispatch_sync函数在指定的处理结束之前,该函数不会返回,容易引起死锁。


9.dispatch_apply

dispatch_apply函数是dispatch_sync和Dispatch Queue的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zu",index);
});
    
NSLog(@"done");

复制代码

执行结果为:

2017-12-16 20:05:39.811031+0800 ImageOrientation[14610:4954147] 3
2017-12-16 20:05:39.811031+0800 ImageOrientation[14610:4954149] 2
2017-12-16 20:05:39.811060+0800 ImageOrientation[14610:4954148] 1
2017-12-16 20:05:39.811113+0800 ImageOrientation[14610:4954045] 0
2017-12-16 20:05:39.812820+0800 ImageOrientation[14610:4954149] 4
2017-12-16 20:05:39.812878+0800 ImageOrientation[14610:4954147] 5
2017-12-16 20:05:39.812891+0800 ImageOrientation[14610:4954148] 6
2017-12-16 20:05:39.813257+0800 ImageOrientation[14610:4954149] 7
2017-12-16 20:05:39.813312+0800 ImageOrientation[14610:4954147] 8
2017-12-16 20:05:39.813331+0800 ImageOrientation[14610:4954148] 9
2017-12-16 20:05:39.814222+0800 ImageOrientation[14610:4954045] done

复制代码

因为在Global Dispatch Queue中执行处理,所以各个处理的执行时间不定。但是输出结果中最后的done必定在最后的位置上,这是因为dispatch_apply函数会等待全部处理执行结束。

函数第一个参数为重复次数,第二个参数为追加对象的Dispatch Queue,第三个参数为追加的处理。第三个参数是带有参数的Block,这是为了按第一个参数重复追加Block并区分各个Block而使用。

例如:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
dispatch_apply([array count], queue, ^(size_t index) {
    NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
});

复制代码

由于dispatch_apply函数等待处理执行结束,所以推荐在dispatch_async函数中执行dispatch_apply

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue,^{
	
	//Global Dispatch Queue等待dispatch_apply函数中全部处理执行结束
	dispatch_apply([array count], queue, ^(size_t index) {
	    NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
	});
	//dispatch_apply函数中的处理全部执行结束
	//在Main Dispatch Queue中非同步执行
	dispatch_asyc(dispatch_get_main_queue(),^{
	    NSLog(@"done");
	});

});

复制代码

10.dispatch_suspend/dispatch_resume

dispatch_suspend函数挂起指定的Dispatch Queue。

dispatch_suspend(queue);
复制代码

dispatch_resume函数恢复指定的Dispatch Queue。

dispatch_resume(queue);
复制代码

这些函数对已经执行的处理没有影响,挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行,而恢复则使得这些处理能够继续执行。


11.Dispatch Semaphore

Dispatch Semaphore是持有计数的信号,该技术是多线程编程中的计数类型信号。所谓信号,类似于过马路时常用的手旗,可以通过时举起手旗,不可通过时放下手旗。在Dispatch Semaphore中,使用计数来实现该功能。计数为0时等待,计数为1或者大于1时,减去1而不等待。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
复制代码

参数表示计数的初始值。本例将计数值初始化为1。

dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FORVEVR);
复制代码

dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值达到大雨或者等于1。当计数时大于等于1,或者在待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回。第二个参数是等待时间。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,1ull*NSEC_PER_SEC);

long result = dispatch_semaphore_wait(sepmaphore,time);

if(result == 0){
	//由于Dispatch Semaphore的计数值达到大于等于1,或者在待机中的指定时间内计数大于等于1,所以计数减去1
	//可执行需要进行排他控制的处理
}else{
	//由于Dispatch Semaphore的计数为0,因此在达到指定时间为止待机
}
复制代码
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

//生成dispatch semaphore,计数初始值设定为1。保证可访问NSMutableArray类对象的线程同时只有1个。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray * array = [[NSMutableArray alloc] init];

for(int i=0;i<100000;i++){
	dispatch_async(queue,^{
	
		//等待dispatch semaphore的计数值达到大于等于1.
		dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
		
		//由于计数值大于等于1,所以将计数减去1.dispatch_semaphore_wait函数返回。
		//此时计数恒为0
		//由于可访问NSMutableArray类对象的线程只有1个,因此可以安全访问。
		[array addObject:[NSNumber numberWithInt:i]];
		
		
		//排他控制处理结束,所以通过dispatch_semaphore_signal函数将计数加1.
		//如果有通过ispatch_semaphore_wait等待计数值增加的线程,就由最先等待的线程执行。
		dispatch_semaphore_signal(semaphore);
	
	});
}

//使用结束,释放。
dispatch_release(semaphore);


复制代码

12.dispatch_once

该函数是保证应用程序执行中只执行一次指定处理的API。

static int initialized = NO;
if(initialized == NO){
	//初始化操作…
	initialized = YES;
}
复制代码

可写为:

static dispatch_one_t pred;
dispatch_once(&pred,^{
	//初始化
});
复制代码

13. Dispatch I/O

常常用于文件分割读取,提高读取速度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值