iOS 如何巧妙解决“一个任务需要等待另外一个任务完成后才可以执行”的问题

看到标题,我想大部分iOS开发的童鞋能想到好几种方案。比如下一个网络请求必须依赖上一个网络请求的结果才可以进行,最简单直白的方法是:“同步调用”。这里所说的同步是指,等上一个网络请求任务完成后,直接在返回数据的delegate或者block中执行下一个网络请求。

但是,如果碰到我下面这种情况呢?

假设现在有三个任务:

任务1: 通过网络请求检测一种状态,这是一个网络耗时的操作。

任务2: 用户点击一个按钮,比如登录按钮。就会去检测有没有请求到这个状态,如果有就继续执行3,没有就等待任务1请求到所需状态后,再继续执行任务3。

任务3:想通过点击按钮,想要执行的任务,该任务不是本文讨论的重点。


可以看到,虽然说,都是属于“一个任务需要等待另外一个任务完成后才可以执行”的问题范畴,但由于代码执行流不同,所以有不能按以往方式处理。


下面给出我的解决办法:


预先设置好所需要的属性或者实例变量

@property (strong, nonatomic) dispatch_semaphore_t lock;
@property (copy, nonatomic) NSString *states;
@property (copy, nonatomic) dispatch_block_t block;


第一步,模拟一个网络请求检测状态的过程,假设耗时10s。

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(10);
        _states = @"Logon";
    });
10s后,网络请求到我们所需的状态,比如是一个字符串“Logon”。


第二步,模拟用户点击登录按钮时,所需状态存在的情况下,要去执行的任务3。

- (IBAction)clickLogonButton {
    if (_states && [_states isEqualToString:@"Logon"]) {
        [self doSomething];
    }
}


好了,按照现有的代码逻辑看,我们在点击登录按钮后,先检测所需的状态是不是存在,如果存在,就继续执行任务3,此处指 - doSomething。这个方法比较简单,我们就仅仅打印一下此时的状态。

- (void)doSomething {
    NSLog(@"states = %@", self.states);
}

假设在界面出现时,我们去网络请求状态。

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    [self checkStates];
}

运行程序,当界面出现的时候,我们点击登录按钮,没有打印任何日志。因为此时,我们的请求还没有拿到所需状态,自然执行不了任务3:- doSomething,这个比较好理解。

我们的需求是,如果状态没有请求到,就让程序暂时”挂起“,等拿到所需状态,再去执行任务3。

我的解决办法是使用信号量,让两个任务执行方式从异步转成同步执行,上代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.lock = dispatch_semaphore_create(1);
}

- (IBAction)clickLogonButton {
    if (_states && [_states isEqualToString:@"Logon"]) {
        [self doSomething];
    } else {
        dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
    }
}


- (void)checkStates {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(10);
        _states = @"Logon";
        dispatch_semaphore_signal(self.lock);
        [self clickLogonButton];
    });
}

运行程序,不管什么时候,点击登录按钮,我们都可以执行到任务3:- doSomething。

但是有个问题,我们在得到状态后,是重新执行了一遍登录事件,这样明显是不合理的。因为我们要从等待状态后的代码执行起,而不是重新来一遍,在本文的例子中可能影响没有那么明显,但在复杂的的项目中,这样做是绝对不靠谱的。简单说,我要告诉程序,已经成功获取到了状态,你可以继续往下执行了,也就是回调。那怎么告诉呢?我们想到block,这也是为什么在本文开头我声明了一个block属性。代码修改如下:


- (IBAction)clickLogonButton {
    NSLog(@"执行任务2 ...");

    __weak __typeof__(self) weakSelf = self;

    self.block = ^() {
        [weakSelf doSomething];
    };

    if (_states && [_states isEqualToString:@"Logon"]) {
        NSLog(@"状态正常");
        self.block();
    } else {
        NSLog(@"状态异常");
        dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
        NSLog(@"等待状态修复 ...");
    }
}

- (void)doSomething {
    NSLog(@"执行任务3: states = %@", self.states);
}


- (void)checkStates {
    NSLog(@"准备执行任务 1");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSLog(@"任务1 正在执行中 ...");

        sleep(10);

        _states = @"Logon";

        dispatch_semaphore_signal(self.lock);

        NSLog(@"任务1 执行完成");

        if (self.block) {
            self.block();
        }
    });
}

目前程序按预期完美运行,如果有更好的方案或者问题,我们可以共同探讨。






评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值