在开发中很多时候会有这样的场景,同一个界面有多个请求,而且要在这几个请求都成功返回的时候再去进行下一操作,对于这种场景,如何来设计请求操作呢?今天我们就来讨论一下有哪几种方案。
分析:
在网络请求的开发中,经常会遇到两种情况,一种是多个请求结束后统一操作,在一个界面需要同时请求多种数据,比如列表数据、广告数据等,全部请求到后再一起刷新界面。另一种是多个请求顺序执行,比如必须先请求个人信息,然后根据个人信息请求相关内容。这些要求对于普通的操作是可以做到并发控制和依赖操作的,但是对于网络请求这种需要时间的请求来说,效果往往与预期的不一样。因为网络请求是异步的,并不知道什么时候网络请求。很多开发人员为了省事,对于网络请求必须满足一定顺序这种情况,一般都是嵌套网络请求,即一个网络请求成功之后再请求另一个网络请求,虽然采用嵌套请求的方式能解决此问题,但存在很多问题,如:其中一个请求失败会导致后续请求无法正常进行、多个请求在时间上没有复用,即无并发性。来看一下下面几种方案:
信号量是一个整数,在创建的时候会有一个初始值,这个初始值往往代表我要控制的同时操作的并发数。 在操作中,对信号量会有两种操作:信号通知与等待。信号通知时,信号量会+1,等待时,如果信号量大于0,则会将信号量-1,否则,会等待直到信号量大于0。什么时候会大于零呢?往往是在之前某个操作结束后,我们发出信号通知,让信号量+1。
在 GCD 中,提供了以下这么几个函数,可用于请求同步等处理,模拟同步请求:
// 创建一个信号量(semaphore)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
// 等待,直到信号量大于0时,即可操作,同时将信号量-1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 信号通知,即让信号量+1
dispatch_semaphore_signal(semaphore);
在使用的时候,往往会创建一个信号量,然后进行多个操作,每次操作都等待信号量大于0再操作,同时信号量-1,操作完后将信号量+1。当信号量就减小到0了,这时候wait操作会起作用,DISPATCH_TIME_FOREVER
表示会永远等待,一直等到信号量大于0,也就是有操作完成了,将信号量+1了,这时候才可以结束等待,进行操作,并且将信号量-1,这样新的任务又要等待。
下面我们展示一段代码来模拟同步请求:
image.png
从打印结果可以看出,在每个请求开始之前,我们创建一个信号量,初始为0,在请求操作之后,我们设一个 dispatch_semaphore_wait
,在请求到结果之后,再将信号量+1,也即是 dispatch_semaphore_signal
。这样做的目的是保证在请求结果没有返回之前,一直让线程等待在那里,这样一个线程的任务一直在等待,就不会算作完成,notify
的内容也就不会执行了,直到每个请求的结果都返回了,线程任务才能够结束,这时候 notify
也才能够执行。
可以使用 dispatch_group_async
函数将多个任务关联到一个 dispatch_group
和相应的 queue
中,dispatch_group
会并发地同时执行这些任务。而且 dispatch_group
可以用来阻塞一个线程,直到 dispatch_group
关联的所有的任务完成执行。有时候必须等待任务完成的结果,然后才能继续后面的处理。
主要使用如下两个函数:
dispatch_group_enter(group);
dispatch_group_leave(group);
注意:
以上这两个函数必须配对使用,否则 dispatch_group_notify
不会触发。
下面我们展示一段代码来模拟同步请求:
image.png
dispatch_group
会等和它关联的所有的 dispatch_queue_t
上的任务都执行完毕才会发出同步信号,dispathc_group_notify
的代码块 block
会被执行。从控制台的打印结构可以看出,如果将上面三个操作改成真实的网络操作后,这个简单的做法会变得无效,因为网络请求需要时间,而线程的执行并不会等待请求完成后才真正算作完成,而是只负责将请求发出去,线程就认为自己的任务算完成了,当三个请求都发送出去,就会执行 dispathc_group_notify
中的内容,但请求结果返回的时间是不一定的,也就导致界面都刷新了,请求才返回,这就是无效的。
image.png
notify
的作用就是在 group
中的其他操作全部完成后,再操作自己的内容,所以我们会看到上面事件 A、B、C 执行之后,才执行事件 E。
和 dispatch_async
相比,当我们调用 n
次 dispatch_group_enter
后再调用 n
次 dispatch_group_level
时,dispatch_group_notify
和 dispatch_group_wait
会收到同步信号;这个特点使得它非常适合处理异步任务的同步当异步任务开始前调用 dispatch_group_enter
异步任务结束后调用 dispatch_group_leve
;
NSOperationQueue
只有两种队列,即主队列和并行队列。通过 [[NSOperationQueue alloc] init];
创建的队列都是并行队列,并且可以将一个或多个 NSOperation
对象放到队列中去执行,而且是异步执行的,一个 NSOperation
对象可以通过调用 start
方法来执行任务,但是默认是同步执行的。则主队列通过 [NSOperationQueue mainQueue];
获得,而且其中所有 NSOperation
都会在主线程中执行。
当然也可以利用 NSOperationQueue
的线程依赖,当某个 NSOperation
对象依赖于其它 NSOperation
对象的完成时,就可以通过 addDependency
方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前 NSOperation
对象才会开始执行操作。需要先添加依赖关系,再将操作添加到队列中。另外,通过 removeDependency
方法来删除依赖对象。
在开发过程中,我们应尽量避免发送同步请求;假设我们一个页面需要同时进行多个请求,他们之间倒是不要求顺序关系,但是要求等他们都请求完毕了再进行界面刷新或者其他什么操作。并且在某个操作依赖于其他几个任务的完成时,采用 dispatch_group
or dispatch_semaphore
来实现同步等处理。如果在某个操作依赖于其他几个任务的完成,可以考虑使用 NSOperationQueue
的线程之间依赖。