多线程应用:解决多个网络请求全部执行完成后统一刷新UI的操作

场景: 现在需要执行三个接口(1001/1002/1003) ,三个接口全部执行完成后再执行刷新UI操作.

目录:

  • GCD+group 实现以上场景
  • GCD+semaphore信号量 实现以上场景
  • semaphore信号量讲解
  • NSOperation讲解
  • 自定义NSOperation

首先写一个模拟网络请求的延时操作的方法和一个主线程刷新UI的方法:

#pragma mark - 模拟网络请求
-(void)requestNetDataWithParams:(NSString *)params andResultBlock:(resultBlock)resultBlock{
    float t = 0.5 + arc4random_uniform(20)/10.0;
    
    NSLog(@"开始执行接口:%@", params);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(t * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSString *message = [NSString stringWithFormat:@"接口执行完成:%@",params];
        NSLog(@"%@,耗时:%.2fs", message, t);
        
        NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
        
        resultBlock(YES, data);
    });
}

#pragma mark - 刷新UI
-(void)refreshUI{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"refresh UI on Thread: %@", [NSThread currentThread]);
    });
}

实现方式1: GCD+group

-(void)reloadData{
    //执行接口 a b c 并行
    __weak typeof(self) weakself = self;
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [weakself requestNetDataWithParams:@"1001" andResultBlock:^(BOOL isOk, NSData *result) {
            dispatch_group_leave(group);
        }];
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [weakself requestNetDataWithParams:@"1002" andResultBlock:^(BOOL isOk, NSData *result) {
            dispatch_group_leave(group);
        }];
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [weakself requestNetDataWithParams:@"1003" andResultBlock:^(BOOL isOk, NSData *result) {
            dispatch_group_leave(group);
        }];
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"接口全部执行完毕!");
        [weakself refreshUI];
    });
}

/*执行结果
2019-08-16 15:46:51.065570+0800 多线程应用[29706:7605331] 开始执行接口:1001
2019-08-16 15:46:51.065582+0800 多线程应用[29706:7605332] 开始执行接口:1003
2019-08-16 15:46:51.065588+0800 多线程应用[29706:7605334] 开始执行接口:1002
2019-08-16 15:46:51.932222+0800 多线程应用[29706:7605207] 接口执行完成:1001,耗时:0.80s
2019-08-16 15:46:52.382853+0800 多线程应用[29706:7605207] 接口执行完成:1002,耗时:1.20s
2019-08-16 15:46:52.929765+0800 多线程应用[29706:7605207] 接口执行完成:1003,耗时:1.70s
2019-08-16 15:46:52.930066+0800 多线程应用[29706:7605207] 接口全部执行完毕!
2019-08-16 15:46:52.930451+0800 多线程应用[29706:7605207] refresh UI on Thread: {number = 1, name = main}
*/

实现方式2: GCD+信号量

-(void)reloadData{
    __weak typeof(self) weakself = self;
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t que = dispatch_get_global_queue(0, 0);
    //信号量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    dispatch_group_async(group, que, ^{
        [weakself requestNetDataWithParams:@"1001" andResultBlock:^(BOOL isOk, NSData *result) {
            dispatch_semaphore_signal(semaphore);
        }];
    });
    
    dispatch_group_async(group, que, ^{
        [weakself requestNetDataWithParams:@"1002" andResultBlock:^(BOOL isOk, NSData *result) {
            dispatch_semaphore_signal(semaphore);
        }];
    });
    
    dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(2);
    dispatch_group_async(group, que, ^{
        [weakself requestNetDataWithParams:@"1003" andResultBlock:^(BOOL isOk, NSData *result) {
            dispatch_semaphore_signal(semaphore2);
        }];
    });
    
    dispatch_group_notify(group, que, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
        
        NSLog(@"接口全部执行完毕!");
        [weakself refreshUI];
    });
}

/*执行结果
2019-08-16 15:55:58.522111+0800 多线程应用[29787:7613733] 开始执行接口:1001
2019-08-16 15:55:58.522130+0800 多线程应用[29787:7613734] 开始执行接口:1003
2019-08-16 15:55:58.522168+0800 多线程应用[29787:7613735] 开始执行接口:1002
2019-08-16 15:56:00.602620+0800 多线程应用[29787:7613660] 接口执行完成:1001,耗时:1.90s
2019-08-16 15:56:00.822656+0800 多线程应用[29787:7613660] 接口执行完成:1003,耗时:2.10s
2019-08-16 15:56:00.822934+0800 多线程应用[29787:7613660] 接口执行完成:1002,耗时:2.20s
2019-08-16 15:56:00.823172+0800 多线程应用[29787:7613735] 接口全部执行完毕!
2019-08-16 15:56:00.823591+0800 多线程应用[29787:7613660] refresh UI on Thread: {number = 1, name = main}
*/

Semaphore讲解:

GCD 中的信号量是指 Dispatch Semaphore, 是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数为0时等待,不可通过。计数为1或大于1时,计数减1且不等待,可通过。

Dispatch Semaphore 提供了三个函数。

1.dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量

2.dispatch_semaphore_signal:发送一个信号,让信号总量加1

3.dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程), 否则就可以正常执行。

注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。

链接:https://www.jianshu.com/p/0dc231328ece
来源:


NSOperation讲解:

NSOperation的finished属性被KVO监听, 如果一旦finished, 就执行completionBlock.

NSOperation 属性:

NSOperation *op;
// 判断操作是否正在在运行
op.isExecuting;

// 判断操作是否已经结束
op.isFinished;

// 判断操作是否已经标记为取消
op.isCancelled;

// 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关
op.isReady;

方法:

  • (void)waitUntilFinished; // 阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。

  • (void)setCompletionBlock:(void (^)(void))block; // completionBlock 会在当前操作执行完毕时执行 completionBlock。

  • (void)addDependency:(NSOperation *)op; // 添加依赖,使当前操作依赖于操作 op 的完成。

  • (void)removeDependency:(NSOperation *)op; // 移除依赖,取消当前操作对操作 op 的依赖。

@property (readonly, copy) NSArray *dependencies; // 在当前操作开始执行之前完成执行的所有操作对象数组。

链接:
https://www.jianshu.com/p/4b1d77054b35
https://www.jianshu.com/p/65629ba90d6a
http://www.cocoachina.com/cms/wap.php?action=article&id=24524


自定义NSOperation:

*自定义并发的NSOperation需要以下步骤:

1.start方法:该方法必须实现

2.main:该方法可选,如果你在start方法中定义了你的任务,则这个方法就可以不实现, 但通常为了代码逻辑清晰,通常会在该方法中定义自己的任务

3.isExecuting isFinished 主要作用是在线程状态改变时,产生适当的KVO通知

4.isConcurrent :必须覆盖并返回YES

//MyOperation.h
@interface MyOperation : NSOperation

@property(nonatomic,strong) NSString *methodCode;

@end
//MyOperation.m

#import "MyOperation.h"

typedef void (^resultBlock)(BOOL isOk, NSData *result);

@interface MyOperation()

// 声明属性(父类虽然有, 但是最后重新声明)
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;

@end

@implementation MyOperation

// 手动合成两个实例变量 _executing, _finished, 因为父类设置成ReadOnly
@synthesize executing = _executing;
@synthesize finished = _finished;

- (instancetype)init {
    self = [super init];
    if (self) {
        self.finished = NO;
        self.executing = NO;
    }
    return self;
}

//isConcurrent :必须覆盖并返回YES
- (BOOL)isConcurrent {
    return YES;
}
// finished 和 excuting 的 setter 需要通过KVO对外通知.
- (void)setFinished:(BOOL)finished {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

- (void)start{
    
    NSLog(@"operation start");
    
    //第一步就要检测是否被取消了,如果取消了,要实现相应的KVO
    if ([self isCancelled]) {
        self.finished = YES;
        return;
    }
    //如果没被取消,开始执行任务
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    self.executing = YES;
}

- (void)main{
    
    NSLog(@"operation main");
    
    @try {
        
        __weak typeof(self) weakSelf = self;
        //在这里我们要创建自己的释放池,因为这里我们拿不到主线程的释放池
        @autoreleasepool {
            [self requestNetDataWithParams:self.methodCode andResultBlock:^(BOOL isOk, NSData *result) {
                NSLog(@"request result");
                
                // NSOperation的finished属性被KVO监听, 如果一旦finished, 就执行completionBlock.
                
                weakSelf.finished = YES;
                weakSelf.executing = NO;
                
            }];
        }
    } @catch (NSException *exception) {
        NSLog(@"%@",exception);
    }
}


-(void)requestNetDataWithParams:(NSString *)params andResultBlock:(resultBlock)resultBlock{
    float t = 0.5 + arc4random_uniform(20)/10.0;
    
    NSLog(@"开始执行接口:%@", params);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(t * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSString *message = [NSString stringWithFormat:@"接口执行完成:%@",params];
        NSLog(@"%@,耗时:%.2fs", message, t);
        
        NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
        
        resultBlock(YES, data);
    });
}

@end
-(void)buttonClicked{
    
    MyOperation *op = [[MyOperation alloc] init];
    op.methodCode = @"1001";
    [op setCompletionBlock:^{
        NSLog(@"op Completion");
    }];

    MyOperation *op2 = [[MyOperation alloc] init];
    op2.methodCode = @"1002";
    [op2 setCompletionBlock:^{
        NSLog(@"op2 Completion");
    }];
    
    NSOperationQueue *queue = [NSOperationQueue new];
    
    [queue addOperation:op];
    [queue addOperation:op2];
}

/*执行结果:
2019-08-16 16:11:33.108319+0800 多线程应用[29890:7630414] operation start
2019-08-16 16:11:33.108325+0800 多线程应用[29890:7630416] operation start
2019-08-16 16:11:33.108790+0800 多线程应用[29890:7630464] operation main
2019-08-16 16:11:33.108775+0800 多线程应用[29890:7630465] operation main
2019-08-16 16:11:33.109011+0800 多线程应用[29890:7630464] 开始执行接口:1002
2019-08-16 16:11:33.109022+0800 多线程应用[29890:7630465] 开始执行接口:1001
2019-08-16 16:11:34.046483+0800 多线程应用[29890:7630337] 接口执行完成:1002,耗时:0.90s
2019-08-16 16:11:34.046686+0800 多线程应用[29890:7630337] request result
2019-08-16 16:11:34.046944+0800 多线程应用[29890:7630415] op2 Completion
2019-08-16 16:11:35.509324+0800 多线程应用[29890:7630337] 接口执行完成:1001,耗时:2.40s
2019-08-16 16:11:35.509547+0800 多线程应用[29890:7630337] request result
2019-08-16 16:11:35.509882+0800 多线程应用[29890:7630415] op Completion
*/

你可能感兴趣的:(多线程应用:解决多个网络请求全部执行完成后统一刷新UI的操作)