iOS 如何巧妙解决“一个任务需要等待另外一个任务完成后才可以执行”的问题
2017-08-17 18:03
821 查看
看到标题,我想大部分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);
}
- (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属性。代码修改如下:
- (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: 通过网络请求检测一种状态,这是一个网络耗时的操作。
任务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();
}
});
}
目前程序按预期完美运行,如果有更好的方案或者问题,我们可以共同探讨。
相关文章推荐
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为(转)
- 如何解决virtualbox中不能打开一个虚拟任务之uuid不匹配的问题
- 问题需要反过来想,oracle连接问题的一个另外的解决办法。
- iOS 应用进入后台后,如何短暂的执行一个任务
- 如何让滚动条消失,且页面可以正常滚动(解决写选项卡时可能遇见的一个问题)
- 单例模式之懒汉的并发问题,只需要添加一个 synchronized 就可以解决了
- 想做一个可以赚钱的网站需要解决哪些问题
- 如何等待一个线程完成后才继续执行之后的操作
- 用C#实现的一个简单任务机,解决了一般任务机无法定时执行任务的问题
- 一、 找出函数中存在的问题。以下题目均在Lab05项目中完成。2、 找出项目中存在的缺陷,并给出解决的方案(至少3个,不包含下面那个举例)。 说明:这里所说的缺陷不一定都是错误,而是明显需要改进的地方。 如:不可以无限输入密码,容易被暴力破解,存在安全隐患。
- 如何解决更新被拒绝,因为远程版本库包含您本地尚不存在的提交。这通常是因为另外 提示:一个版本库已向该引用进行了推送。再次推送前,您可能需要先整合远程变更 提示:(如 'git pull ...')。
- 如何解决更新被拒绝,因为远程版本库包含您本地尚不存在的提交。这通常是因为另外 提示:一个版本库已向该引用进行了推送。再次推送前,您可能需要先整合远程变更 提示:(如 'git pull ...')。
- 在使用Hibernate时,因为一个查询需要更多的表连接而要使用SQL来解决性能问题。然而返回的结果集中包含了没有映射的Entity类中的表字段,在这个SQL中还有使用如何将层次关系的父子结点显示为横行
- Linux下shell脚本在crontab中一个周期执行不完,下周期任务被重复执行的问题解决
- iOS 如何创建一个线程,要求可以一直工作,不会执行一次就结束
- "执行SQL语句时出现问题操作必须使用一个可更新的查询"错误的解决方法
- 最近被一个问题困扰着,如何实现类似Photoshop的多层图像操作,每层可单独操作,又可以组合显示出来。
- 如何解决“不是可以识别的 函数名“的问题
- 向同一个servlet发多个不同请求,如何解决同步问题?
- 终于解决了一个问题--如何在数据绑定时不让combox控件触发SelectedIndexChanged事件