ios多个网络请求之间的处理(OC版)

·  阅读 3163

在日常开发中我们总是会和网络打交道,从服务端拿数据渲染UI、上传数据到服务器、登陆等,那么就会遇到一些问题。eg:当用户登陆完毕后才获取数据渲染UI或者是多个网络请求从服务端拿到多个数据后,才进行下一步的操作,那么对网络请求之间顺序的控制是十分重要的,本文对这两种情况进行总结,如有不足之处,请多多指教。同时本文只提供了部分截图,其他运行效果可自行尝试。

Swift版:juejin.cn/post/710075…

情景一:多个网络请求执行(无序)完后,在执行其他操作

#define GlobalQueue dispatch_get_global_queue(0, 0)

#define MainQueue dispatch_get_main_queue()

  1. 队列组group + notify

    思路:group其实就是管理指定queue中任务的。在这里通过调用dispatch_group_notify方法,等待group中管理queue的任务执行完毕后,会在该group指定的队列中执行block内部的代码。

      
      - (void)multipleRequest_NoOrder_after_executeOtherTask_byGroupNotify {
          dispatch_group_t group = dispatch_group_create();
          dispatch_group_async(group, GlobalQueue, ^{
              sleep(1);
              NSLog(@"网络请求1");
          });
          dispatch_group_async(group, GlobalQueue, ^{
              sleep(1);
              NSLog(@"网络请求2");
          });
          dispatch_group_async(group, GlobalQueue, ^{
              sleep(1);
              NSLog(@"网络请求3");
          });
          dispatch_group_async(group, GlobalQueue, ^{
              sleep(1);
              NSLog(@"网络请求4");
          });
          //当group中的任务执行完后,会调用
          dispatch_group_notify(group, MainQueue, ^{
              NSLog(@"更新UI");
          });
      }
    复制代码

    运行截图: 第一次运行:

image.png 第二次运行:

image.png

  1. NSOperation + NSOperationQueue

    思路:通过NSBlockOperation创建多个任务,将任务添加到NSBlockOperationQueue中.通过调用addBarrierBlock方法,会等到将任务添加到NSBlockOperationQueue中所有的任务执行完毕后,才会执行block中的代码。

      - (void)multipleRequest_NoOrder_after_executeOtherTask_byOperation {
          NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
              sleep(1);
              NSLog(@"网络请求1");
          }];
          NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
              sleep(1);
              NSLog(@"网络请求2");
          }];
          NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
              sleep(1);
              NSLog(@"网络请求3");
          }];
          NSBlockOperation *block4 = [NSBlockOperation blockOperationWithBlock:^{
              sleep(1);
              NSLog(@"网络请求4");
          }];
          
          NSOperationQueue *queue = [[NSOperationQueue alloc] init];
          //设置最大并发数
          queue.maxConcurrentOperationCount = [NSProcessInfo processInfo].activeProcessorCount;
          //waitUntilFinished:是否等待queue中的任务执行完后,才执行后面的代码。会阻塞当前线程
          [queue addOperations:@[block1,block2,block3,block4] waitUntilFinished:NO];
          
          //当queue中的所有任务执行完后,会调用
          [queue addBarrierBlock:^{
              NSLog(@"更新UI");
          }];
      }
    复制代码
  2. 信号量dispatch_semaphore_t 思路:信号量dispatch_semaphore_t其实底层是通过加锁操作实现的。类似于操作系统中的PV操作,感兴趣的读者可以阅读:https://blog.csdn.net/daijin888888/article/details/51423678

    典型的问题就是:生产者消费者问题、哲学家进餐问题等

      - (void)multipleRequest_NoOrder_after_executeOtherTask_bySemaphore {
        //传入的值必须大于等于0
          dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
          dispatch_async(GlobalQueue, ^{
              sleep(1);
              NSLog(@"网络请求1");
              dispatch_semaphore_signal(semaphore);
          });
          dispatch_async(GlobalQueue, ^{
              sleep(1);
              NSLog(@"网络请求2");
              dispatch_semaphore_signal(semaphore);
          });
          dispatch_async(GlobalQueue, ^{
              sleep(1);
              NSLog(@"网络请求3");
              dispatch_semaphore_signal(semaphore);
          });
          dispatch_async(GlobalQueue, ^{
              sleep(1);
              NSLog(@"网络请求4");
              dispatch_semaphore_signal(semaphore);
          });
          dispatch_async(MainQueue, ^{
              dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
              dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
              dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
              dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
              NSLog(@"更新UI");
          });
      }
    复制代码
  3. 栅栏函数barrier 思路:栅栏函数其实和NSOperationQueue的addBarrierBlock方法相似,都是等待队列中的任务执行完毕后,才执行block中的代码。但是需要注意提到的坑点。

      - (void)multipleRequest_NoOrder_after_executeOtherTask_byBarrier {
          dispatch_queue_t queue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
          dispatch_async(queue, ^{
              sleep(1);
              NSLog(@"网络请求1");
          });
          dispatch_async(queue, ^{
              sleep(1);
              NSLog(@"网络请求2");
          });
          dispatch_async(queue, ^{
              sleep(1);
              NSLog(@"网络请求3");
          });
          dispatch_async(queue, ^{
              sleep(1);
              NSLog(@"网络请求4");
          });
          //坑点:不能用全局并发队列,栅栏函数会失效并且栅栏函数只能用于自定义创建的并发队列,如果传递dispatch_get_global_queue(0, 0),那么dispatch_barrier_async等价于dispatch_async
          dispatch_barrier_async(queue, ^{
              NSLog(@"更新UI");
          });
      }
    复制代码

情景二:多个网络请求执行(有序)完后,在执行其他操作 (线程同步方案)

  1. 队列组group + enter + leave

    思路:很简单,enter代表一个block任务进入,leave代表一个block任务离开。

      
      - (void)multipleRequest_InOrder_after_executeOtherTask_byGroup {
          dispatch_group_t group = dispatch_group_create();
      ​
          dispatch_group_enter(group);
          [self request:@"1" finished:^{
              dispatch_group_leave(group);
          }];
          
          dispatch_group_enter(group);
          [self request:@"2" finished:^{
              dispatch_group_leave(group);
          }];
          
          dispatch_group_enter(group);
          [self request:@"3" finished:^{
              dispatch_group_leave(group);
          }];
          
          dispatch_group_enter(group);
          [self request:@"4" finished:^{
              dispatch_group_leave(group);
          }];
          dispatch_group_notify(group, MainQueue, ^{
              NSLog(@"更新UI");
          });
      }
      - (void)request:(NSString *)parama finished:(void(^)(void))finished {
          dispatch_async(GlobalQueue, ^{
              NSLog(@"网络请求%@",parama);
              if(finished) { finished(); }
          });
       
      }
    复制代码
  2. 通过异步+串行队列 原理:异步开启子线程,但是由于是串行队列,任务的执行按照FIFO的原则,那么先进入队列的网络请求任务,会被子线程优先从队列中取出来执行。最终在网络请求4完成后,在主线程刷新UI。

      - (void)multipleRequest_InOrder_after_executeOtherTask_byAsyncSerialQueue{
    dispatch_queue_t asyncSerialQueue = dispatch_queue_create("AsyncSerialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(asyncSerialQueue, ^{
        sleep(1);
        NSLog(@"网络请求1");
    });
    dispatch_async(asyncSerialQueue, ^{
        sleep(1);
        NSLog(@"网络请求2");
    });
    dispatch_async(asyncSerialQueue, ^{
        sleep(1);
        NSLog(@"网络请求3");
    });
    dispatch_async(asyncSerialQueue, ^{
        sleep(1);
        NSLog(@"网络请求4");
        dispatch_async(MainQueue, ^{
            NSLog(@"更新UI");
        });
    });
    复制代码

}

运行截图:

image.png

  1. NSOperation + NSOperationQueue(添加任务之间的依赖关系)

      
      - (void)multipleRequest_InOrder_after_executeOtherTask_byOperation {
          NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
              sleep(1);
              NSLog(@"网络请求1");
          }];
          NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
              sleep(1);
              NSLog(@"网络请求2");
          }];
          NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
              sleep(1);
              NSLog(@"网络请求3");
          }];
          NSBlockOperation *block4 = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"网络请求4");
              sleep(1);
          }];
          [block4 addDependency:block3];
          [block3 addDependency:block2];
          [block2 addDependency:block1];
          NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
          [operationQueue addOperations:@[block1,block2,block3,block4] waitUntilFinished:NO];
          //会等到queue中的任务执行完后才会调用
          [operationQueue addBarrierBlock:^{
              dispatch_async(dispatch_get_main_queue(), ^{
                  NSLog(@"刷新UI");
              });
          }];
      }
    复制代码
  2. 条件NSConditionLock

      - (void)multipleRequest_InOrder_after_executeOtherTask_byConditionLock {
          NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:1];
          dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
          /**
           *lockWhenCondition(加锁):当特定条件值时,才会加锁,否则线程休眠
           *unlockWithCondition(解锁):解锁并将条件值设置为传入的值
           */
          dispatch_async(globalQueue, ^{
              [conditionLock lockWhenCondition:1];
              NSLog(@"网络请求1");
              sleep(1);
              [conditionLock unlockWithCondition:2];
          });
          dispatch_async(globalQueue, ^{
              [conditionLock lockWhenCondition:2];
              NSLog(@"网络请求2");
              sleep(1);
              [conditionLock unlockWithCondition:3];
          });
          dispatch_async(globalQueue, ^{
              [conditionLock lockWhenCondition:3];
              NSLog(@"网络请求3");
              sleep(1);
              [conditionLock unlockWithCondition:4];
          });
          dispatch_async(globalQueue, ^{
              [conditionLock lockWhenCondition:4];
              NSLog(@"网络请求4");
              sleep(1);
              [conditionLock unlockWithCondition:1];
              dispatch_async(dispatch_get_main_queue(), ^{
                  NSLog(@"刷新UI");
              });
          });
      }
    复制代码

5.通过嵌套闭包(回调闭包)

- (void)multipleRequest_InOrder_after_executeOtherTask_byNestedBlock {

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        // 这里可以根据自己的需求来,eg:我们可以根据网路请求1获取的数据,做为网络请求2的参数等等

        [self request1:^(NSString *data) {

            NSLog(@"data = %@ %@",data,[NSThread currentThread]);

            [self request2:^(NSString *data) {

                NSLog(@"data = %@ %@",data,[NSThread currentThread]);

                [self request3:^(NSString *data) {

                    NSLog(@"data = %@ %@",data,[NSThread currentThread]);

                }];

            }];

            // 到这里根据获取的数据刷新UI

            dispatch_async(dispatch_get_main_queue(), ^{

                NSLog(@"在主线程更新UI");

            });

        }];

    });

}

- (void)request1:(void(^)(NSString *data))completion {

    sleep(1);

    completion(@"网络请求1-数据1");

}

- (void)request2:(void(^)(NSString *data))completion {

    sleep(1);

    completion(@"网络请求2-数据2");

}

- (void)request3:(void(^)(NSString *data))completion {

    sleep(1);

    completion(@"网络请求3-数据3");

}
复制代码

结果:

image.png

上面是本人目前掌握的解决方案。后续会学习apple推出的async、await、actor等。如有更好的方式,欢迎大家进行评论交流。

分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改