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

7,364 阅读8分钟

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

Swift版:juejin.cn/post/710075…

注:其中GlobalQueue、MainQueue分别代表全局并发队列和主队列

#define GlobalQueue dispatch_get_global_queue(0, 0)

#define MainQueue dispatch_get_main_queue()

#define CurrentThread [NSThread currentThread]

本文涉及到的第三方库Bolts后续会专门出一篇文章进行讲解

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

1. 队列组group + notify

思路:group其实就是管理指定queue中任务的。在这里通过调用dispatch_group_notify方法,等待group中管理queue的任务执行完毕后,会在该group指定的队列中执行block内部的代码。其中进入dispatch_group_async函数时group会对任务数+1,离开dispatch_group_async时group会对当前的任务数-1.当group中的任务数为0时会触发dispatch_group_notify中的block

- (void)multipleRequest_NoOrder_after_executeOtherTask_byGroupNotify {
    dispatch_group_t group = dispatch_group_create();
    // dispatch_group_async会自动帮我管理group中任务的数量
    dispatch_group_async(group, GlobalQueue, ^{
        sleep(1);
        NSLog(@"网络请求1,线程:%@",CurrentThread);
    });
    dispatch_group_async(group, GlobalQueue, ^{
        sleep(1);
        NSLog(@"网络请求2,线程:%@",CurrentThread);
    });
    dispatch_group_async(group, GlobalQueue, ^{
        sleep(1);
        NSLog(@"网络请求3,线程:%@",CurrentThread);
    });
    dispatch_group_async(group, GlobalQueue, ^{
        sleep(1);
        NSLog(@"网络请求4,线程:%@",CurrentThread);
    });
    //当group中的任务执行完后,会调用
    dispatch_group_notify(group, MainQueue, ^{
        NSLog(@"更新UI,线程:%@",CurrentThread);
    });
}

第一次运行: image.png 第二次运行: image.png

2. NSOperation + NSOperationQueue

思路:通过NSBlockOperation创建多个任务,将任务添加到NSBlockOperationQueue中.通过调用addBarrierBlock方法,会等到将任务添加到NSBlockOperationQueue中所有的任务执行完毕,才会执行barrierBlock中的代码.同时会堵塞当前NSBlockOperationQueue中比barrierBlock后添加的其他任务,当barrierBlock执行完后才后继续执行queue中比barrierBlock后添加的block任务。和dispatch_barrier_async作用类似

- (void)multipleRequest_NoOrder_after_executeOtherTask_byOperation {
    NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"网络请求1,线程:%@",CurrentThread);
    }];
    NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"网络请求2,线程:%@",CurrentThread);
    }];
    NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"网络请求3,线程:%@",CurrentThread);
    }];
    NSBlockOperation *block4 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"网络请求4,线程:%@",CurrentThread);
    }];
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 设置最大并发数
    queue.maxConcurrentOperationCount = [NSProcessInfo processInfo].activeProcessorCount;
    // waitUntilFinished:是否等待queue中的任务执行完后,才执行后面的代码。会阻塞当前线程
    [queue addOperations:@[block1,block2,block3,block4] waitUntilFinished:NO];
    // 当queue中的所有任务执行完后,会调用
   [queue addBarrierBlock:^{
   [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"更新UI,线程:%@",CurrentThread);
       }];
   }];
    NSLog(@"123");
   [queue addOperation:[NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"BarrierBlock执行完毕,我才能执行,线程:%@",CurrentThread);
    }]];
    NSLog(@"456");
}

第一次运行: image.png

第二次运行: image.png

如果你对多线程足够熟悉,我们不用运行都能知道打印结果: 因为operation中的任务是在子线程中执行所以不堵塞当前的主线程.所以每一次输出顺序都是123、456. 由于4个网络请求在子线程中执行,顺序不是固定的.所以接下来应该随机打印这4个网络请求. 然后打印‘更新UI’.最后打印'BarrierBlock执行完毕,我才能执行'.

3. 信号量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,线程:%@",CurrentThread);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(GlobalQueue, ^{
        sleep(1);
        NSLog(@"网络请求2,线程:%@",CurrentThread);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(GlobalQueue, ^{
        sleep(1);
        NSLog(@"网络请求3,线程:%@",CurrentThread);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(GlobalQueue, ^{
        sleep(1);
        NSLog(@"网络请求4,线程:%@",CurrentThread);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(YHMainQueue, ^{
        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,线程:%@",CurrentThread);
    });
}

第一次运行: image.png 第二次运行:

image.png ```

4. 栅栏函数barrier()

思路:栅栏函数其实和NSOperationQueue的addBarrierBlock方法相似,都是等待队列中的任务执行完毕后,才执行barrierBlock中的代码.同时会堵塞当前queue中其他比barrierBlock后添加的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,线程:%@",CurrentThread);
    });
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"网络请求2,线程:%@",CurrentThread);
    });
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"网络请求3,线程:%@",CurrentThread);
    });
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"网络请求4,线程:%@",CurrentThread);
    });
    // 坑点:不能用全局并发队列,栅栏函数会失效并且栅栏函数只能用于自定义创建的并发队列,如果传递dispatch_get_global_queue或者串行队列则dispatch_barrier_async等价于dispatch_async
    dispatch_barrier_async(queue, ^{
        dispatch_async(MainQueue, ^{
            NSLog(@"更新UI,线程:%@",CurrentThread);
          });
    });
}

第一次运行: image.png 第二次运行: image.png

5. 队列组group + enter + leave + notify.

因为dispatch_async是异步+全局并发队列,所以开启新的线程执行任务。同时异步不会堵塞当前线程,所以循环中的block任务一旦提交就遍历后续元素。dispatch_group_enter和dispatch_group_leave分别代表group的中任务数-1和+1.当任务数等于0时就会走到dispatch_group_notify方法

- (void)multiRequest_NoOrder_after_executeOtherTask_byGroup {
    NSArray <NSString *> *netWorkTasks = @[@"网络请求1",@"网络请求2",@"网络请求3",@"网络请求4"];
    dispatch_group_t group = dispatch_group_create();
    NSMutableArray *result = @[].mutableCopy;
    [netWorkTasks enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        // 任务数+1
        dispatch_group_enter(group);
        /// 网络请求是异步的
        dispatch_async(GlobalQueue, ^{
            /// 模拟耗时操作
            sleep(1);
            NSLog(@"%@,线程:%@",obj,CurrentThread);
            /// 模拟内部处理完数据后,回调主线程.
            dispatch_async(MainQueue, ^{
                // 任务数-1
                dispatch_group_leave(group);
            });
        });
    }];
    // 任务数 = 0时
    dispatch_group_notify(group, MainQueue, ^{
        NSLog(@"更新UI,线程:%@",CurrentThread);
    });
}

运行截图: 第一次运行: image.png 第二次运行: image.png

6.借助三方库Bolts

这里每一个BFTask代表一个网络请求.当taskForCompletionOfAllTasks中的所有BFTask执行完毕后会调用continueWithBlock中的代码.该库后续会专门出一篇帖子讲解.

- (void)multipleRequest_NoOrder_after_executeOtherTask_byBFTask {
    NSArray <BFTask <NSString *> *> *tasks = @[
        [self getStringBFTaskByNetRequestWithNumber:@1],
        [self getStringBFTaskByNetRequestWithNumber:@2],
        [self getStringBFTaskByNetRequestWithNumber:@3]
    ];
    [[BFTask taskForCompletionOfAllTasks:tasks] continueWithBlock:^id _Nullable(BFTask * _Nonnull t) {
        NSLog(@"网络请求的结果: %@,线程:%@",[tasks valueForKey:@"result"],CurrentThread);
        return nil;
    }];
}
- (BFTask <NSString *> *)getBFTaskByNetRequestWithNumber:(NSNumber *)number {
    BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
    dispatch_async(GlobalQueue, ^{
        sleep(1);
        NSLog(@"网络请求 %zd,线程:%@",number.integerValue,CurrentThread);
        dispatch_async(MainQueue, ^{
            [task trySetResult:[NSString stringWithFormat:@"网络请求 数据%zd",number.integerValue]];
        });
    });
    return task.task;
}

第一次运行: image.png

第二次运行: image.png

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

1.通过异步+串行队列

原理:异步开启子线程,但是由于是串行队列,任务的执行按照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,线程:%@",CurrentThread);
    });
    dispatch_async(asyncSerialQueue, ^{
        sleep(1);
        NSLog(@"网络请求2,线程:%@",CurrentThread);
    });
    dispatch_async(asyncSerialQueue, ^{
        sleep(1);
        NSLog(@"网络请求3,线程:%@",CurrentThread);
    });
    dispatch_async(asyncSerialQueue, ^{
        sleep(1);
        NSLog(@"网络请求4,线程:%@",CurrentThread);
        dispatch_async(MainQueue, ^{
            NSLog(@"更新UI,线程:%@",CurrentThread);
        });
    });
}

运行截图: image.png

2.NSOperation + NSOperationQueue(添加任务之间的依赖关系)
- (void)multipleRequest_InOrder_after_executeOtherTask_byOperation {
    NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"网络请求1,线程:%@",CurrentThread);
    }];
    NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"网络请求2,线程:%@",CurrentThread);
    }];
    NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"网络请求3,线程:%@",CurrentThread);
    }];
    NSBlockOperation *block4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"网络请求4,线程:%@",CurrentThread);
        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,线程:%@",CurrentThread);
        });
    }];
}

运行结果: image.png

3.条件NSConditionLock
- (void)multipleRequest_InOrder_after_executeOtherTask_byConditionLock {
    // 初始化条件为1
    NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:1];
    /**
     *lockWhenCondition(加锁):当特定条件值时,才会加锁,否则线程休眠
     *unlockWithCondition(解锁):解锁并将条件值设置为传入的值
     */
    dispatch_async(GlobalQueue, ^{
        [conditionLock lockWhenCondition:1];
        NSLog(@"网络请求1,线程:%@",CurrentThread);
        sleep(1);
        [conditionLock unlockWithCondition:2];
    });
    dispatch_async(GlobalQueue, ^{
        [conditionLock lockWhenCondition:2];
        NSLog(@"网络请求2,线程:%@",CurrentThread);
        sleep(1);
        [conditionLock unlockWithCondition:3];
    });
    dispatch_async(GlobalQueue, ^{
        [conditionLock lockWhenCondition:3];
        NSLog(@"网络请求3,线程:%@",CurrentThread);
        sleep(1);
        [conditionLock unlockWithCondition:4];
    });
    dispatch_async(GlobalQueue, ^{
        [conditionLock lockWhenCondition:4];
        NSLog(@"网络请求4,线程:%@",CurrentThread);
        sleep(1);
        [conditionLock unlockWithCondition:1];
        dispatch_async(MainQueue, ^{
            NSLog(@"更新UI,线程:%@",CurrentThread);
        });
    });
}

运行截图: image.png

4.通过嵌套闭包(回调闭包)
- (void)multipleRequest_InOrder_after_executeOtherTask_byNestedBlock {
    // 这里可以根据自己的需求来,eg:我们可以根据网路请求1获取的数据,做为网络请求2的参数等等
    NSNumber *paramas1 = @10;
    [self requestWithNumber:@1 paramas:paramas1 completion:^(NSNumber *response) {
        NSNumber *paramas2 = response;
        [self requestWithNumber:@2 paramas:paramas2 completion:^(NSNumber *response) {
            NSNumber *paramas3 = response;
            [self requestWithNumber:@3 paramas:paramas3 completion:^(NSNumber *response) {
                dispatch_async(MainQueue, ^{
                    NSLog(@"response = %@",response);
                    NSLog(@"刷新UI");
                });
            }];
        }];
    }];
}
- (void)requestWithNumber:(NSNumber *)number
                  paramas:(NSNumber *)paramas
               completion:(void (^)(NSNumber *response))completion {
    dispatch_async(GlobalQueue, ^{
        sleep(1);
        NSLog(@"网络请求 %zd,参数:'%@',线程:%@",number.integerValue,paramas,CurrentThread);
        dispatch_async(MainQueue, ^{
            completion(@(paramas.integerValue * 2));
        });
    });
}

运行结果: image.png

5.设置目标队列

思路:通过给队列设置目标队列,让原本在不同队列中异步/同步执行的任务在同一个串行队列中顺序执行.那么目标队列显然只能是串行队列

- (void)multipleRequest_InOrder_after_executeOtherTask_bySetTargetQueue {
    // 目标队列是串行
    dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);
    
    // 这里可以是串行或并发队列
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue4 = dispatch_queue_create("test.4", DISPATCH_QUEUE_CONCURRENT);

    // 设置目标队列
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_set_target_queue(queue3, targetQueue);
    dispatch_set_target_queue(queue4, targetQueue);
    
    dispatch_async(queue1, ^{
        sleep(1);
        NSLog(@"网络请求1,线程:%@",CurrentThread);
    });
    dispatch_async(queue2, ^{
        sleep(1);
        NSLog(@"网络请求2,线程:%@",CurrentThread);
    });
    dispatch_async(queue3, ^{
        sleep(1);
        NSLog(@"网络请求3,线程:%@",CurrentThread);
    });
    dispatch_async(queue4, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"更新UI,线程:%@",CurrentThread);
        });
    });
}

运行结果: image.png

6.借助三方库Bolts
- (BFTask <NSNumber *> *)getNumberBFTaskByNetRequestWithNumber:(NSNumber *)number paramas:(NSNumber *)paramas {
    BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
    dispatch_async(GlobalQueue, ^{
        sleep(1);
        NSLog(@"网络请求 %zd,线程:%@",number.integerValue,CurrentThread);
        dispatch_async(MainQueue, ^{
            [task trySetResult:@(paramas.integerValue * 2)];
        });
    });
    return task.task;
}
- (void)multipleRequest_InOrder_after_executeOtherTask_byBFTask {
    NSNumber *paramas1 = @10;
    // 这里可以根据自己的需求来,eg:我们可以根据网路请求1获取的数据,做为网络请求2的参数等等
    [[[[self getNumberBFTaskByNetRequestWithNumber:@1 paramas:paramas1] continueWithBlock:^id _Nullable(BFTask<NSNumber *> * _Nonnull t) {
        NSLog(@"网络请求1结果:%@,线程:%@",t.result,CurrentThread);
        NSNumber *paramas2 = t.result;
        return [self getNumberBFTaskByNetRequestWithNumber:@2 paramas:paramas2];
    }] continueWithBlock:^id _Nullable(BFTask * _Nonnull t) {
        NSLog(@"网络请求2结果:%@,线程:%@",t.result,CurrentThread);
        NSNumber *paramas3 = t.result;
        return [self getNumberBFTaskByNetRequestWithNumber:@3 paramas:paramas3];
    }] continueWithBlock:^id _Nullable(BFTask * _Nonnull t) {
        NSLog(@"网络请求3结果:%@,线程:%@",t.result,CurrentThread);
        return [BFTask taskWithResult:nil];
    }];
}

运行结果: image.png