ios网络基础与多线程

iOS基础
文章目录
  1. 1. 常见名词
  2. 2. 网络模型
    1. 2.1. 七层 OSI模型
    2. 2.2. TCP/IP模型
    3. 2.3. TCP/IP模型层次结构
    4. 2.4. 工作流
    5. 2.5. Socket
    6. 2.6. 三次握手
    7. 2.7. IPV6-Only
  3. 3. HTTP
  4. 4. HTTPS
  5. 5. HTTP VS HTTPS
  6. 6. Socket
    1. 6.1. 术语
  7. 7. Pthreads
  8. 8. NSThread
  9. 9. GCD
    1. 9.1. 基本概念
      1. 9.1.1. 1、系统标准两个队列
      2. 9.1.2. 2、自定义队列
      3. 9.1.3. 3、同步异步线程创建
    2. 9.2. 队列(dispatch queue)
      1. 9.2.1. 自定义队列优先级
    3. 9.3. 队列类型
    4. 9.4. dispatch_once用法
    5. 9.5. dispatch_async
    6. 9.6. dispatch_after延后执行
    7. 9.7. dispatch_apply进行快速迭代
    8. 9.8. dispatch_group_t
    9. 9.9. dispatch_block_cancel
    10. 9.10. 信号量(Dispatch Semaphore)
    11. 9.11. dispatch_time_t
    12. 9.12. dispatch_suspend dispatch_resume
    13. 9.13. GCD死锁
      1. 9.13.1. 主队列的同步线程
      2. 9.13.2. 串行队列中同步执行
      3. 9.13.3. 主线程被占用
    14. 9.14. dispatch_barrier_async
    15. 9.15. dispatch_queue_set_specific dispatch_get_specific
    16. 9.16. Operation Queues vs. Grand Central Dispatch (GCD)
    17. 9.17. Operation对象
      1. 9.17.1. Operation对象还支持下面的特性
    18. 9.18. 并发 vs. 非并发 Operation
    19. 9.19. 创建 NSInvocationOperation 对象
    20. 9.20. 创建 NSBlockOperation 对象
    21. 9.21. 自定义 Operation 对象
      1. 9.21.1. 非并发的NSOperation
        1. 9.21.1.1. 执行主任务
        2. 9.21.1.2. 响应取消事件
      2. 9.21.2. 配置并发执行的 Operation
    22. 9.22. 维护 KVO 通知
  10. 10. Operation 对象的执行行为
    1. 10.1. 配置依赖关系
    2. 10.2. 修改 Operation 在队列中的优先级
    3. 10.3. 设置 Completion Block
    4. 10.4. 执行 Operation 对象
      1. 10.4.1. 添加 Operation 到 Operation Queue 中
      2. 10.4.2. 手动执行 Operation
    5. 10.5. 取消 Operation
    6. 10.6. 等待 Operation 执行完成
    7. 10.7. 暂停和恢复 Operation Queue
      1. 10.7.1. setMaxConcurrentoperationCount
  11. 11. 参考文章

本文分为两个部分 第一部分将着重讲述跟网络有关的基础知识,包含但不止于网络模型、Socket、HTTP以及HTTPS基础知识。第二部分着重于iOS相关的多线程的基础内容GCD,NSOperation等

常见名词

  • TCP/IP:网络上使用的网络协议簇,TCP是应用程序之间的通信,IP是计算机之间的通信

  • 逻辑地址:协议软件配置的网络地址

  • 物理地址:网络硬件相关的地址

  • IP地址:定位计算机设备的逻辑地址

  • 端口:内部通道或地址,它在TCP/IP传输层和应用程序之间提供了一个接口

  • 域名:与IP地址相关联的名字

  • 路由器:通过逻辑地址转发数据的网络设备

  • 局域网:LAN,小型网络

  • 网关:连接LAN到大型网络的路由器

  • TCP:传输层中可靠的、面向连接的协议

  • UDP:传输层中不可靠、非面向连接协议

  • Socket:套接字

  • IPv4:TCP/IP协议簇中网络层协议IP的版本4(IP version 4),32位

  • IPv6:IPv4的下一个版本,号称可以为全世界的每一粒沙子编上一个网址,解决IPv4的网络资源地址有限问题,128位

  • IPv6-Only网络:运营商逐渐部署IPv6 DNS64/NAT64网络之后,设备被分配的地址变成IPv6的地址,依然可以通过此网络获取IPv4地址提供的内容。

网络模型

七层 OSI模型

OSI

TCP/IP模型

TCP/IP

TCP/IP模型层次结构

层次结构

工作流

工作流

Socket

每个TCP、UDP数据段中都包含源端口和目标端口,有时我们把一个IP地址和一个端口号合称为一个套接字,一个套接字可唯一确定网络中每个TCP连接的双方(客户IP地址、客户端口号、服务器IP地址、服务器端口号)

socket

不同的应用层协议可能基于不同传输层协议,比如TCP
有的应用层协议可能占用了两个端口号,比如FTP
有的应用层协议使用了不同的传输层协议提供服务,比如DNS

三次握手

TCP会话通过三次握手初始化,目标是使数据段的发送和接收同步。

三次握手

  • 第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;

  • 第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包

  • 第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。

IPV6-Only

IPV6-Only

如果DNS判断IPV6地址不存在,那么会在去查询IPV4对应的地址是否存在

HTTP

HTTP

HTTPS

HTTPS

  • 1、就是用户发起请求,服务器响应后返回一个证书,证书中包含一些基本信息和公钥。
  • 2、用户拿到证书后,去验证这个证书是否合法,不合法,则请求终止。
  • 3、合法则生成一个随机数,作为对称加密的密钥,用服务器返回的公钥对这个随机数加密。然后返回给服务器。
  • 4、服务器拿到加密后的随机数,利用私钥解密,然后再用解密后的随机数(对称密钥),把需要返回的数据加密,加密完成后数据传输给用户。
  • 5、最后用户拿到加密的数据,用一开始的那个随机数(对称密钥),进行数据解密。整个过程完成。

HTTP VS HTTPS

HTTPS需要到CA申请证书,需要交费
HTTP信息是明文传输,HTTPS有SSL加密传输协议
HTTP端口是80,HTTPS端口是443
HTTPS协议握手阶段比较费时,页面加载时间增加50%,好点增加10-20%

Socket

Socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用实现进程在网络中通信。

Socket

多线程

术语

进程(process),指的是一个正在运行中的可执行文件。每一个进程都拥有独立的虚拟内存空间和系统资源,包括端口权限等,且至少包含一个主线程和任意数量的辅助线程。另外,当一个进程的主线程退出时,这个进程就结束了;
线程(thread),指的是一个独立的代码执行路径,也就是说线程是代码执行路径的最小分支。在 iOS 中,线程的底层实现是基于 POSIX threads API 的,也就是我们常说的 pthreads ;
任务(task),指的是我们需要执行的工作,是一个抽象的概念,用通俗的话说,就是一段代码。

Pthreads

基于C语言,移植性强

1
2
3
#import <pthread.h>
pthread_t thread;
pthread_create(&thread, NULL, start, NULL);

NSThread

1
2
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
[thread start];

GCD

异步和同步最大的区别在于异步不会阻塞当前线程,是否等待block完成后返回

基本概念

1、系统标准两个队列

1
2
3
4
//全局队列,一个并行的队列
dispatch_get_global_queue
//主队列,主线程中的唯一队列,一个串行队列
dispatch_get_main_queue

2、自定义队列

1
2
3
4
//串行队列
dispatch_queue_create("com.starming.serialqueue", DISPATCH_QUEUE_SERIAL)
//并行队列
dispatch_queue_create("com.starming.concurrentqueue", DISPATCH_QUEUE_CONCURRENT)

3、同步异步线程创建

1
2
3
4
//同步线程
dispatch_sync(..., ^(block))
//异步线程
dispatch_async(..., ^(block))

队列(dispatch queue)

Serial(串行队列):又叫private dispatch queues,同时只执行一个任务。Serial queue常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然各自是同步,但serial queue之间是并发执行。
Main dispatch queue(主队列):全局可用的serial queue,在应用程序主线程上执行任务。
Concurrent(全局队列):又叫global dispatch queue,可以并发的执行多个任务,但执行完成顺序是随机的。系统提供四个全局并发队列,这四个队列有这对应的优先级,用户是不能够创建全局队列的,只能获取。

1
2
dipatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);

User create queue:创建自己定义的队列,可以用dispatch_queue_create函数,函数有两个参数,第一个自定义的队列名,第二个参数是队列类型,默认NULL或者DISPATCH_QUEUE_SERIAL的是串行,参数为DISPATCH_QUEUE_CONCURRENT为并行队列。

1
2
dispatch_queue_t queue
queue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue", DISPATCH_QUEUE_CONCURRENT);

自定义队列优先级

dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

dispatch_set_target_queue 函数有两个作用:

  • 第一,变更队列的执行优先级;
  • 第二,目标队列可以成为原队列的执行阶层。
      第一个参数是要执行变更的队列(不能指定主队列和全局队列)
      第二个参数是目标队列(指定全局队列)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//优先级变更的串行队列,初始是默认优先级 NULL 默认是串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.setTargetQueue.serialQueue", NULL);

//优先级不变的串行队列(参照),初始是默认优先级
dispatch_queue_t serialDefaultQueue = dispatch_queue_create("com.gcd.setTargetQueue.serialDefaultQueue", NULL);

//变更前
dispatch_async(serialQueue, ^{
NSLog(@"1");
});
dispatch_async(serialDefaultQueue, ^{
NSLog(@"2");
});

//获取优先级为后台优先级的全局队列
dispatch_queue_t globalDefaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//变更优先级
dispatch_set_target_queue(serialQueue, globalDefaultQueue);

//变更后 serialQueue 最低的优先级
dispatch_async(serialQueue, ^{
NSLog(@"1");
});
dispatch_async(serialDefaultQueue, ^{
NSLog(@"2");
});

执行结果

1
2
3
4
2018-03-27 14:53:33.911110+0800 GCDTest[63921:39078159] 2
2018-03-27 14:53:33.911110+0800 GCDTest[63921:39078155] 1
2018-03-27 14:53:33.911240+0800 GCDTest[63921:39078159] 2
2018-03-27 14:53:33.911266+0800 GCDTest[63921:39078158] 1

队列类型

5种队列,主队列(main queue),四种通用调度队列,自己定制的队列。四种通用调度队列为

  • QOS_CLASS_USER_INTERACTIVE:user interactive等级表示任务需要被立即执行提供好的体验,用来更新UI,响应事件等。这个等级最好保持小规模。
  • QOS_CLASS_USER_INITIATED:user initiated等级表示任务由UI发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
  • QOS_CLASS_UTILITY:utility等级表示需要长时间运行的任务,伴有用户可见进度指示器。经常会用来做计算,I/O,网络,持续的数据填充等任务。这个任务节能。
  • QOS_CLASS_BACKGROUND:background等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。

dispatch_once用法

dispatch_once_t要是全局或static变量,保证dispatch_once_t只有一份实例

1
2
3
4
5
6
7
8
9
+ (instancetype)shared
{
static WMPayService *shared;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shared = [[self alloc] init];
});
return shared;
}

最好可以做到手写单例

dispatch_async

1
2
3
4
5
6
7
//代码框架
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗时的操作
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面
});
});

dispatch_after延后执行

dispatch_after只是延时提交block,不是延时立刻执行。

1
2
3
4
5
6
7
8
- (void)foo
{
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self bar];
});
}

dispatch_apply进行快速迭代

类似for循环,但是在并发队列的情况下dispatch_apply会并发执行block任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)applyTest
{
for (size_t y = 0; y < 10; ++y) {
for (size_t x = 0; x < 10; ++x) {
// Do something with x and y here
}
}
//因为可以并行执行,所以使用dispatch_apply可以运行的更快
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, concurrentQueue, ^(size_t i) {
NSLog(@"%zu",i);
});
NSLog(@"The end"); //这里有个需要注意的是,dispatch_apply这个是会阻塞主线程的。这个log打印会在dispatch_apply都结束后才开始执行
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
2018-03-27 15:38:35.550890+0800 GCDTest[71235:39254293] 2
2018-03-27 15:38:35.550895+0800 GCDTest[71235:39254387] 6
2018-03-27 15:38:35.550892+0800 GCDTest[71235:39254375] 1
2018-03-27 15:38:35.550893+0800 GCDTest[71235:39254377] 5
2018-03-27 15:38:35.550892+0800 GCDTest[71235:39254376] 0
2018-03-27 15:38:35.550892+0800 GCDTest[71235:39254378] 3
2018-03-27 15:38:35.550893+0800 GCDTest[71235:39254379] 4
2018-03-27 15:38:35.550950+0800 GCDTest[71235:39254388] 7
2018-03-27 15:38:35.551024+0800 GCDTest[71235:39254293] 8
2018-03-27 15:38:35.551029+0800 GCDTest[71235:39254387] 9
2018-03-27 15:38:35.551520+0800 GCDTest[71235:39254293] The end

dispatch_group_t

dispatch groups是专门用来监视多个异步任务。dispatch_group_t实例用来追踪不同队列中的不同任务

当group里所有事件都完成GCD API有两种方式发送通知,第一种是dispatch_group_wait,会阻塞当前进程,等所有任务都完成或等待超时。第二种方法是使用dispatch_group_notify,异步执行闭包,不会阻塞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)dispatchGroupWaitDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
//在group中添加队列的block
dispatch_group_async(group, concurrentQueue, ^{
[NSThread sleepForTimeInterval:2.f];
NSLog(@"1");
});
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"2");
});

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2*USEC_PER_SEC);
dispatch_group_wait(group, time);
NSLog(@"go on");
}

输出结果

1
2
3
2018-03-27 16:14:44.160150+0800 GCDTest[72371:39397292] 2
2018-03-27 16:14:44.162394+0800 GCDTest[72371:39396970] go on
2018-03-27 16:14:46.163427+0800 GCDTest[72371:39397294] 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//dispatch_group_notify
- (void)dispatchGroupNotifyDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, concurrentQueue, ^{
sleep(2);
NSLog(@"1");
});
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"end");
});
NSLog(@"can continue");

}

输出结果:

1
2
3
4
2018-03-27 16:13:58.912264+0800 GCDTest[72343:39394024] can continue
2018-03-27 16:13:58.912264+0800 GCDTest[72343:39394093] 2
2018-03-27 16:14:00.913552+0800 GCDTest[72343:39394099] 1
2018-03-27 16:14:00.913930+0800 GCDTest[72343:39394024] end

dispatch_block_cancel

iOS8后GCD支持对dispatch block的取消

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)dispatchBlockCancel {
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t firstBlock = dispatch_block_create(0, ^{
NSLog(@"first block start");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"first block end");
});
dispatch_block_t secondBlock = dispatch_block_create(0, ^{
NSLog(@"second block run");
});
dispatch_async(serialQueue, firstBlock);
dispatch_async(serialQueue, secondBlock);
//取消secondBlock
dispatch_block_cancel(secondBlock);
}

输出结果

1
2
2018-03-27 16:13:24.401008+0800 GCDTest[72320:39391634] first block start
2018-03-27 16:13:26.403986+0800 GCDTest[72320:39391634] first block end

信号量(Dispatch Semaphore)

dispatch_semaphore_create:创建一个信号量(semaphore)
dispatch_semaphore_signal:信号通知,即让信号量+1
dispatch_semaphore_wait:等待,直到信号量大于0时,即可操作,同时将信号量-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (void)semaphorTest {
//crate的value表示,最多几个资源可访问
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//任务1
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(2);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(3);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, 2);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});

NSLog(@"end");
}

输出结果

1
2
3
4
5
6
7
2018-03-27 16:12:41.938609+0800 GCDTest[72293:39388442] run task 2
2018-03-27 16:12:41.938612+0800 GCDTest[72293:39388438] run task 3
2018-03-27 16:12:41.938609+0800 GCDTest[72293:39388391] end
2018-03-27 16:12:41.938610+0800 GCDTest[72293:39388439] run task 1
2018-03-27 16:12:42.942686+0800 GCDTest[72293:39388438] complete task 3
2018-03-27 16:12:43.943696+0800 GCDTest[72293:39388439] complete task 1
2018-03-27 16:12:44.942998+0800 GCDTest[72293:39388442] complete task 2

dispatch_time_t

用dispatch_after的时候就会用到dispatch_time_t变量,但是如何创建合适的时间呢?答案就是用dispatch_time函数,其原型如下:

1
dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );

第一个参数一般是DISPATCH_TIME_NOW,表示从现在开始。
那么第二个参数就是真正的延时的具体时间

这里要特别注意的是,delta参数是“纳秒!”,就是说,延时1秒的话,delta应该是“1000000000”=。=,太长了,所以理所当然系统提供了常量,如下:

1
2
3
#define NSEC_PER_SEC 1000000000ull 纳秒
#define USEC_PER_SEC 1000000ull 微妙
#define NSEC_PER_USEC 1000ull 秒

所以要延时一秒可以写成:

1
2
3
dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);

dispatch_suspend dispatch_resume

dispatch_suspend != 立即停止队列的运行
dispatch_suspenddispatch_resume提供了“挂起、恢复”队列的功能,简单来说,就是可以暂停、恢复队列上的任务。但是这里的“挂起”,并不能保证可以立即停止队列上正在运行的block,看如下例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)dispatch_suspendTest
{
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//提交第一个block,延时5秒打印。
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"After 5 seconds...");
});
//提交第二个block,也是延时5秒打印
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"After 5 seconds again...");
});
//延时一秒
NSLog(@"sleep 1 second...");
[NSThread sleepForTimeInterval:1];
//挂起队列
NSLog(@"suspend...");
dispatch_suspend(queue);
//延时10秒
NSLog(@"sleep 10 second...");
[NSThread sleepForTimeInterval:10];
//恢复队列
NSLog(@"resume...");
dispatch_resume(queue);
}

输出结果:

1
2
3
4
5
6
2018-03-27 16:38:38.979600+0800 GCDTest[73048:39489499] sleep 1 second...
2018-03-27 16:38:39.980863+0800 GCDTest[73048:39489499] suspend...
2018-03-27 16:38:39.981146+0800 GCDTest[73048:39489499] sleep 10 second...
2018-03-27 16:38:43.984071+0800 GCDTest[73048:39489555] After 5 seconds...
2018-03-27 16:38:49.981099+0800 GCDTest[73048:39489499] resume...
2018-03-27 16:38:54.985417+0800 GCDTest[73048:39489554] After 5 seconds again...

dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行

GCD死锁

当前串行队列里面同步执行当前串行队列就会死锁,解决的方法就是将同步的串行队列放到另外一个线程就能够解决。

主队列的同步线程

1
2
3
4
5
6
7
8
- (void)deadLockCase1 {
NSLog(@"1");
//主队列的同步线程,按照FIFO的原则(先入先出),2排在3后面会等3执行完,但因为同步线程,3又要等2执行完,相互等待成为死锁。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}

结果

解决:

1
2
3
4
5
6
7
8
- (void)deadLockCase2 {
NSLog(@"1");
//3会等2,因为2在全局并行队列里,不需要等待3,这样2执行完回到主队列,3就开始执行
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2");
});
NSLog(@"3");
}

输出结果:

1
2
3
2018-03-27 16:28:50.688138+0800 GCDTest[72824:39450961] 1
2018-03-27 16:28:50.688254+0800 GCDTest[72824:39450961] 2
2018-03-27 16:28:50.688336+0800 GCDTest[72824:39450961] 3

串行队列中同步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)deadLockCase3 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");
//串行队列里面同步一个串行队列就会死锁
dispatch_sync(serialQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}

结果也是死锁 解决方法如下

1
2
3
4
5
6
7
8
9
10
11
12
- (void)deadLockCase4 {
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
//将同步的串行队列放到另外一个线程就能够解决
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}

主线程被占用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

- (void)deadLockCase5 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
//回到主线程发现死循环后面就没法执行了
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
});
NSLog(@"4");
//死循环
while (1) {
//
}
}

dispatch_barrier_async

dispatch_barrier_async函数的作用与barrier的意思相同,在进程管理中起到一个栅栏的作用,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行,该函数需要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)barrier
{
  //同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});

dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
}

打印结果:

1
2
3
4
5
2018-05-24 18:19:13.234432+0800 Test[8324:8295580] ----1-----<NSThread: 0x600000279d00>{number = 3, name = (null)}
2018-05-24 18:19:13.234434+0800 Test[8324:8295577] ----2-----<NSThread: 0x60800027ba00>{number = 4, name = (null)}
2018-05-24 18:19:13.234607+0800 Test[8324:8295580] ----barrier-----<NSThread: 0x600000279d00>{number = 3, name = (null)}
2018-05-24 18:19:13.234733+0800 Test[8324:8295577] ----4-----<NSThread: 0x60800027ba00>{number = 4, name = (null)}
2018-05-24 18:19:13.234739+0800 Test[8324:8295580] ----3-----<NSThread: 0x600000279d00>{number = 3, name = (null)}

dispatch_queue_set_specific dispatch_get_specific

FMDB如何使用dispatch_queue_set_specific和dispatch_get_specific来防止死锁

作用类似objc_setAssociatedObject跟objc_getAssociatedObject

1
2
3
4
5
6
7
8
9
10
11
static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
//创建串行队列,所有数据库的操作都在这个队列里
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
//标记队列
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);

//检查是否是同一个队列来避免死锁的方法
- (void)inDatabase:(void (^)(FMDatabase *db))block {
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
}

NSOperation

Operation Queues vs. Grand Central Dispatch (GCD)

简单来说,GCD 是苹果基于 C 语言开发的,一个用于多核编程的解决方案,主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。而Operation Queues 则是一个建立在 GCD 的基础之上的,面向对象的解决方案。它使用起来比 GCD 更加灵活,功能也更加强大。下面简单地介绍了 Operation QueuesGCD 各自的使用场景:

Operation Queues :相对 GCD 来说,使用 Operation Queues 会增加一点点额外的开销,但是我们却换来了非常强大的灵活性和功能,我们可以给 operation 之间添加依赖关系、取消一个正在执行的 operation 、暂停和恢复 operation queue 等;

GCD :则是一种更轻量级的,以 FIFO 的顺序执行并发任务的方式,使用 GCD 时我们并不关心任务的调度情况,而让系统帮我们自动处理。但是 GCD 的短板也是非常明显的,比如我们想要给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得非常棘手。

Operation对象

NSOperation 本身是一个抽象类,不能直接实例化,因此,如果我们想要使用它来执行具体任务的话,就必须创建自己的子类或者使用系统预定义的两个子类,NSInvocationOperation 和 NSBlockOperation 。

  • NSInvocationOperation :我们可以通过一个 objectselector 非常方便地创建一个 NSInvocationOperation ,这是一种非常动态和灵活的方式。假设我们已经有了一个现成的方法,这个方法中的代码正好就是我们需要执行的任务,那么我们就可以在不修改任何现有代码的情况下,通过方法所在的对象和这个现有方法直接创建一个 NSInvocationOperation
  • NSBlockOperation :我们可以使用 NSBlockOperation 来并发执行一个或多个 block ,只有当一个 NSBlockOperation 所关联的所有 block 都执行完毕时,这个 NSBlockOperation 才算执行完成,有点类似于 dispatch_group 的概念。

Operation对象还支持下面的特性

  • 支持在 operation 之间建立依赖关系,只有当一个 operation 所依赖的所有 operation 都执行完成时,这个 operation 才能开始执行;
  • 支持一个可选的 completion block ,这个 block 将会在 operation 的主任务执行完成时被调用;
  • 支持通过 KVO 来观察 operation 执行状态的变化;
  • 支持设置执行的优先级,从而影响 operation 之间的相对执行顺序;
  • 支持取消操作,可以允许我们停止正在执行的 operation 。

并发 vs. 非并发 Operation

通常来说,我们都是通过将 operation 添加到一个 operation queue 的方式来执行 operation 的,然而这并不是必须的。

我们也可以直接通过调用 start 方法来执行一个 operation ,但是这种方式并不能保证 operation 是异步执行的。NSOperation 类的 isConcurrent 方法的返回值标识了一个 operation 相对于调用它的 start 方法的线程来说是否是异步执行的。在默认情况下,isConcurrent 方法的返回值是 NO ,也就是说会阻塞调用它的 start 方法的线程。

如果我们想要自定义一个并发执行的 operation ,那么我们就必须要编写一些额外的代码来让这个 operation 异步执行。比如,为这个 operation 创建新的线程、调用系统的异步方法或者其他任何方式来确保 start 方法在开始执行任务后立即返回。

创建 NSInvocationOperation 对象

NSInvocationOperation 是 NSOperation 类的一个子类,当一个 NSInvocationOperation 开始执行时,它会调用我们指定的 object 的 selector 方法

1
2
3
- (NSInvocationOperation *)invocationOperationWithData:(id)data {
return [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myTaskMethod1:) object:data];
}

输出结果:

1
2
2018-03-28 11:16:41.061212+0800 NSOperationDemo[2314:40962438] Start executing myTaskMethod1: with data: 111, mainThread: <NSThread: 0x60800007b280>{number = 1, name = main}, currentThread: <NSThread: 0x60800007b280>{number = 1, name = main}
2018-03-28 11:16:44.062435+0800 NSOperationDemo[2314:40962438] Finish executing myTaskMethod1:

创建 NSBlockOperation 对象

NSBlockOperation 是 NSOperation 类的另外一个系统预定义的子类,我们可以用它来封装一个或多个 block

什么情况下会优先使用NSBlockOperation:

当我们在应用中已经使用了 Operation Queues 且不想创建 Dispatch Queues 时,
NSBlockOperation 类可以为我们的应用提供一个面向对象的封装;
我们需要用到 Dispatch Queues 不具备的功能时,比如需要设置 operation 之间的依赖关系、使
用 KVO 观察 operation 的状态变化等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (NSBlockOperation *)blockOperation {
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Start executing block1, mainThread: %@, currentThread: %@", [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing block1");
}];

[blockOperation addExecutionBlock:^{
NSLog(@"Start executing block2, mainThread: %@, currentThread: %@", [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing block2");
}];

[blockOperation addExecutionBlock:^{
NSLog(@"Start executing block3, mainThread: %@, currentThread: %@", [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing block3");
}];

return blockOperation;
}

输出结果:

1
2
3
4
5
6
2018-03-28 11:16:44.063438+0800 NSOperationDemo[2314:40962438] Start executing block1, mainThread: <NSThread: 0x60800007b280>{number = 1, name = main}, currentThread: <NSThread: 0x60800007b280>{number = 1, name = main}
2018-03-28 11:16:44.063491+0800 NSOperationDemo[2314:40962523] Start executing block3, mainThread: <NSThread: 0x60800007b280>{number = 1, name = (null)}, currentThread: <NSThread: 0x60000027b940>{number = 4, name = (null)}
2018-03-28 11:16:44.063500+0800 NSOperationDemo[2314:40962525] Start executing block2, mainThread: <NSThread: 0x60800007b280>{number = 1, name = (null)}, currentThread: <NSThread: 0x604000273140>{number = 3, name = (null)}
2018-03-28 11:16:47.063715+0800 NSOperationDemo[2314:40962438] Finish executing block1
2018-03-28 11:16:47.063714+0800 NSOperationDemo[2314:40962525] Finish executing block2
2018-03-28 11:16:47.063714+0800 NSOperationDemo[2314:40962523] Finish executing block3

自定义 Operation 对象

当系统预定义的两个子类 NSInvocationOperation 和 NSBlockOperation 不能很好的满足我们的需求时,我们可以自定义自己的 NSOperation 子类,添加我们想要的功能。目前,我们可以自定义非并发和并发两种不同类型的 NSOperation 子类

非并发的NSOperation

对于一个非并发的 operation ,我们需要做的就只是执行 main 方法中的任务以及能够正常响应取消事件就可以了,其它的复杂工作比如依赖配置、KVO 通知等 NSOperation 类都已经帮我们处理好了。

执行主任务

从最低限度上来说,每一个 operation 都应该至少实现以下两个方法:

一个自定义的初始化方法;
main 方法。

响应取消事件

当一个 operation 开始执行后,它会一直执行它的任务直到完成或被取消为止。我们可以在任意时间点取消一个 operation ,甚至是在它还未开始执行之前。为了让我们自定义的 operation 能够支持取消事件,我们需要在代码中定期地检查 isCancelled 方法的返回值,一旦检查到这个方法返回 YES ,我们就需要立即停止执行接下来的任务。根据苹果官方的说法,isCancelled 方法本身是足够轻量的,所以就算是频繁地调用它也不会给系统带来太大的负担。

The isCancelled method itself is very lightweight and can be called
frequently without any significant performance penalty.

通常来说,当我们自定义一个 operation 类时,我们需要考虑在以下几个关键点检查 isCancelled 方法的返回值:

  • 在真正开始执行任务之前;
  • 至少在每次循环中检查一次,而如果一次循环的时间本身就比较长的话,则需要检查得更加频繁;
  • 在任何相对来说比较容易中止 operation 的地方。

看到这里,我想你应该可以意识到一点,那就是尽管 operation 是支持取消操作的,但却并不是立即取消的,而是在你调用了 operationcancel 方法之后的下一个 isCancelled 的检查点取消的

示例 一个支持取消操作Operation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@interface NonConcurrentOperation ()

@property (strong, nonatomic) id data;

@end
@implementation NonConcurrentOperation
- (id)initWithData:(id)data {
self = [super init];
if (self) {
self.data = data;
}
return self;
}

/// 支持取消操作
- (void)main {
@try {
if (self.isCancelled) return;

NSLog(@"Start executing %@ with data: %@, mainThread: %@, currentThread: %@", NSStringFromSelector(_cmd), self.data, [NSThread mainThread], [NSThread currentThread]);

for (NSUInteger i = 0; i < 3; i++) {
if (self.isCancelled) return;

sleep(1);

NSLog(@"Loop %@", @(i + 1));
}

NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
}
@catch(NSException *exception) {
NSLog(@"Exception: %@", exception);
}
}

注意:operation的取消操作需要自己在block或者selector中加isCancelled的判断

配置并发执行的 Operation

在默认情况下,operation 是同步执行的,也就是说在调用它的 start 方法的线程中执行它们的任务。而在 operationoperation queue 结合使用时,operation queue 可以为非并发的 operation 提供线程,因此,大部分的 operation 仍然可以异步执行

如果你想要手动地执行一个 operation ,又想这个 operation 能够异步执行的话,你需要做一些额外的配置来让你的 operation 支持并发执行。

需要重写的方法:

start必须的,所有并发执行的 operation 都必须要重写这个方法,替换掉 NSOperation 类中的默认实现。start 方法是一个 operation 的起点,我们可以在这里配置任务执行的线程或者一些其它的执行环境。另外,需要特别注意的是,在我们重写的 start 方法中一定不要调用父类的实现

main可选的,通常这个方法就是专门用来实现与该 operation 相关联的任务的。尽管我们可以直接在 start 方法中执行我们的任务,但是用 main 方法来实现我们的任务可以使设置代码和任务代码得到分离,从而使 operation 的结构更清晰;

isExecutingisFinished必须的,并发执行的 operation 需要负责配置它们的执行环境,并且向外界客户报告执行环境的状态。因此,一个并发执行的 operation 必须要维护一些状态信息,用来记录它的任务是否正在执行,是否已经完成执行等。此外,当这两个方法所代表的值发生变化时,我们需要生成相应的 KVO 通知,以便外界能够观察到这些状态的变化;

isConcurrent必须的,这个方法的返回值用来标识一个 operation 是否是并发的 operation ,我们需要重写这个方法并返回 YES 。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@implementation ConcurrentOperation
//用 @synthesize 关键字手动合成了两个实例变量 _executing 和 _finished ,
//然后分别在重写的 isExecuting 和 isFinished 方法中返回了这两个实例变量
//通过查看 NSOperation 类的头文件可以发现,executing 和 finished 属性都被声明成了只读的 readonly 。
//所以我们在 NSOperation 子类中就没有办法直接通过 setter 方法来自动触发 KVO 通知,
//这也是为什么我们需要在接下来的代码中手动触发 KVO 通知的原因。
@synthesize executing = _executing;
@synthesize finished = _finished;

- (id)init {
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
}
return self;
}

//是否为并发
- (BOOL)isConcurrent {
return YES;
}

//是否正在执行
- (BOOL)isExecuting {
return _executing;
}

// 是否完成
- (BOOL)isFinished {
return _finished;
}

- (void)start {
// 在真正开始执行任务前,我们通过检查 isCancelled 方法的返回值来判断 operation 是否已经被 cancel ,如果是就直接返回了
// 即使一个 operation 是被 cancel 掉了,我们仍然需要手动触发 isFinished 的 KVO 通知。
// 因为当一个 operation 依赖其他 operation 时,它会观察所有其他 operation 的 isFinished 的值的变化,
// 只有当它依赖的所有 operation 的 isFinished 的值为 YES 时,这个 operation 才能够开始执行。
// 因此,如果一个我们自定义的 operation 被取消了但却没有手动触发 isFinished 的 KVO 通知的话,
// 那么所有依赖它的 operation 都不会执行
if (self.isCancelled) {
[self willChangeValueForKey:@"isFinished"];
_finished = YES;
[self didChangeValueForKey:@"isFinished"];

return;
}

[self willChangeValueForKey:@"isExecuting"];
// 为 main 方法分离了一个新的线程 这是 operation 能够并发执行的关键所在
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
_executing = YES;

[self didChangeValueForKey:@"isExecuting"];
}

//在任务执行完毕后,我们需要手动触动 isExecuting 和 isFinished 的 KVO 通知。
- (void)main {
@try {
NSLog(@"Start executing %@, mainThread: %@, currentThread: %@", NSStringFromSelector(_cmd), [NSThread mainThread], [NSThread currentThread]);

sleep(3);

[self willChangeValueForKey:@"isExecuting"];
_executing = NO;
[self didChangeValueForKey:@"isExecuting"];

[self willChangeValueForKey:@"isFinished"];
_finished = YES;
[self didChangeValueForKey:@"isFinished"];

NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
}
@catch (NSException *exception) {
NSLog(@"Exception: %@", exception);
}
}

维护 KVO 通知

NSOperation 类的以下 key paths 支持 KVO 通知,我们可以通过观察这些 key paths 非常方便地监听到一个 operation 内部状态的变化:

  • isCancelled
  • isConcurrent
  • isExecuting
  • isFinished
  • isReady
  • dependencies
  • queuePriority
  • completionBlock

与重写 main 方法不同的是,如果我们重写了 start 方法或者对 NSOperation 类做了大量定制的话,我们需要保证自定义的 operation 在这些 key paths 上仍然支持 KVO 通知。比如,当我们重写了 start 方法时,我们需要特别关注的是 isExecutingisFinished 这两个 key paths ,因为这两个 key paths 最可能受重写 start 方法的影响。

Operation 对象的执行行为

配置依赖关系

1
2
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

注意

  • addDependency: 方法添加的依赖关系是单向的,比如 [A addDependency:B]; ,表示 A 依赖 B,B 并不依赖 A 。

  • 这里的依赖关系并不局限于相同 operation queue 中的 operation 之间。

  • 不要在 operation 之间添加循环依赖,因为这样会导致这些 operation 都不会被执行

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)operationDependencyTest
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1 test");
}];

NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op2 test");
}];

NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op3 test");
}];

//op1依赖于op2
[op1 addDependency:op2];

[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}

输出结果:

1
2
3
2018-03-28 14:38:26.281018+0800 NSOperationDemo[5771:41445417] op2 test
2018-03-28 14:38:26.281019+0800 NSOperationDemo[5771:41445416] op3 test
2018-03-28 14:38:26.281244+0800 NSOperationDemo[5771:41445419] op1 test

修改 Operation 在队列中的优先级

setQueuePriority:

适用范围:只适用于同一个operation queue中的operation
但是高德优先级并不意味着可以首先被执行,因为 决定operation被执行顺序的第一要素是它们的 isReady 状态,其次是它们在队列中的优先级

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)QueuePriorityTest
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1 test");
}];

NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op2 test");
}];

NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op3 test");
}];
// NSOperationQueuePriorityVeryLow = -8L,
// NSOperationQueuePriorityLow = -4L,
// NSOperationQueuePriorityNormal = 0,
// NSOperationQueuePriorityHigh = 4,
// NSOperationQueuePriorityVeryHigh = 8
[op1 setQueuePriority:NSOperationQueuePriorityHigh];
[op3 setQueuePriority:NSOperationQueuePriorityLow];

[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}

输出结果:

1
2
3
2018-03-28 14:41:18.941460+0800 NSOperationDemo[5852:41457022] op1 test
2018-03-28 14:41:18.941460+0800 NSOperationDemo[5852:41457021] op3 test
2018-03-28 14:41:18.941460+0800 NSOperationDemo[5852:41457020] op2 test

设置 Completion Block

setCompletionBlock:
一个 operation 可以在它的主任务执行完成时回调一个 completion block

  • 当一个 operation 被取消时,它的 completion block 仍然会执行,所以我们需要在真正执行代码前检查一下 isCancelled 方法的返回值
  • 我们也没有办法保证 completion block 被回调时一定是在主线程,理论上它应该是与触发 isFinished 的 KVO 通知所在的线程一致的,所以如果有必要的话我们可以在 completion block 中使用 GCD 来保证从主线程更新 UI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 设置completionBlock
- (void)completionBlockTest
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1 test");
}];

NSBlockOperation* op2 = [[NSBlockOperation alloc] init];
__weak NSBlockOperation* myWeakOp = op2;
myWeakOp = [NSBlockOperation blockOperationWithBlock:^{
sleep(5);
// 如果要可以取消需要自己加判断
if ([myWeakOp isCancelled]) {
return ;
}
NSLog(@"op2 test");
}];

[op2 setCompletionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
//回调的线程跟触发KVO isFinished的在一个线程不一定是主线程 如果不是主线程 而且在这个回调里
// 做了更新UI的操作需要在主线程更新
if (![NSThread isMainThread]) {
NSLog(@"not main thread update ui in main thread");
}
//即使任务呗取消了 也会走CompletionBlock 只不过可以通过cancelled属性判断
if (myWeakOp.cancelled) {
NSLog(@"op2 cancelled");
}
NSLog(@"op2 complete");
}];

__weak typeof(op1) weakOp1 = op1;
[op1 setCompletionBlock:^{
NSLog(@"op1 complete");
}];

[queue addOperation:op1];
[queue addOperation:op2];
sleep(1);
[queue cancelAllOperations];
NSLog(@"op2 cancel");

}

执行 Operation 对象

  • 直接调用start
  • 将 operation 添加到一个 operation queue 中,让 operation queue 来帮我们自动执行

添加 Operation 到 Operation Queue 中

1
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];

将operation添加到queue中的方法:

  • addOperation: ,添加一个 operation 到 operation queue 中;
  • addOperations:waitUntilFinished: ,添加一组 operation 到 operation queue 中;
  • addOperationWithBlock: ,直接添加一个 block 到 operation queue 中,而不用创建一个 NSBlockOperation 对象。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (void)executeOperationUsingOperationQueue {
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];

NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(taskMethod) object:nil];
[operationQueue addOperation:invocationOperation];

NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
sleep(3);
NSLog(@"Finish executing blockOperation1");
}];

NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
sleep(3);
NSLog(@"Finish executing blockOperation2");
}];

[operationQueue addOperations:@[ blockOperation1, blockOperation2 ] waitUntilFinished:YES];
NSLog(@"blockOperation1 blockOperation2 taskMethod all finished");
[operationQueue addOperationWithBlock:^{
sleep(3);
NSLog(@"Finish executing block");
}];

[operationQueue waitUntilAllOperationsAreFinished];
NSLog(@"test end");
}

- (void)taskMethod {
sleep(3);
NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
}

输出:

1
2
3
4
5
6
2018-03-28 15:36:57.950936+0800 NSOperationDemo[7299:41666224] Finish executing blockOperation1
2018-03-28 15:36:57.950941+0800 NSOperationDemo[7299:41666225] Finish executing blockOperation2
2018-03-28 15:36:57.950964+0800 NSOperationDemo[7299:41666227] Finish executing taskMethod
2018-03-28 15:36:57.951320+0800 NSOperationDemo[7299:41666189] blockOperation1 blockOperation2 taskMethod all finished
2018-03-28 15:37:00.956712+0800 NSOperationDemo[7299:41666227] Finish executing block
2018-03-28 15:37:00.957116+0800 NSOperationDemo[7299:41666189] test end

在将一个 operation 添加到 operation queue 后就不要再修改这个 operation 了。因为 operation 被添加到 operation queue 后随时可能会执行,这个是由系统决定的,所以再修改它的依赖关系或者所包含的数据就很有可能会造成未知的影响。

手动执行 Operation

如果要手动执行Operation 可以直接调用start方法,但是从严格意义上来说,在调用 start 方法真正开始执行一个 operation 前,我们应该要做一些防范性的判断:

  • 检查 operation 的 isReady 状态是否为 YES ,这个取决于它所依赖的 operation 是否已经执行完成
  • 检查 operation 的 isCancelled 状态是否为 YES ,如果是,那么我们就根本不需要再花费不必要的开销去启动它
  • 检查一下它的 isConcurrent 状态。如果它的 isConcurrent 状态为 NO ,那么我们就需要考虑一下是否可以在当前线程同步执行这个 operation ,或者是先为这个 operation 创建一个单独的线程,以供它异步执行。

在默认的 start 方法中会生成一些必要的 KVO 通知,比如 isExcuting 和 isFinished ,而这些 KVO 通知正是 operation 能够正确处理好依赖关系的关键所在。

取消 Operation

1
2
3
[operation cancel];

[queue cancelAllOperations];

注意:当一个 operation 被取消后,这个 operation 的 isFinished 状态也会变成 YES ,这样处理的好处就是所有依赖它的 operation 能够接收到这个 KVO 通知,从而能够清除这个依赖关系正常执行。

等待 Operation 执行完成

1
2
[operation waitUntilFinished];
[queue waitUntilAlloperationsAreFinished];

注意:当我们在等待一个 operation queue 中的所有 operation 执行完成时,其他的线程仍然可以向这个 operation queue 中添加 operation ,从而延长我们的等待时间

暂停和恢复 Operation Queue

[queue setSuspended]

注意:暂停执行 operation queue 并不能使正在执行的 operation 暂停执行,而只是简单地暂停调度新的 operation 。另外,我们并不能单独地暂停执行一个 operation ,除非直接 cancel 掉。

setMaxConcurrentoperationCount

设置一个 operation queue 最大可并发的 operation 数.
可以通过将这个值设置成 1 实现让 operation queue 一次只执行一个 operation 的目的
注意:

  • 1、operation 的执行顺序还是一样会受其他因素影响的,比如 operation 的 isReady 状态、operation 的队列优先级等
  • 一个串行的 operation queue 与一个串行的 dispatch queue 还是有本质区别的,因为 dispatch queue 的执行顺序一直是 FIFO 的。如果 operation 的执行顺序对我们来说非常重要,那么我们就应该在将 operation 添加到 operation queue 之前就建立好它的依赖关系。

参考文章

细说GCD
GCD使用经验与技巧浅谈
多线程基础到进阶
iOS并发编程