- 在iOS开发过程中,许多复杂的逻辑耗时任务对于开发人员来说,面临复杂的调度设计,在使用多线程进行耗时操作管理时,程序员往往需要经过单任务任务初期功能评估设计,以及后期多任务情况下合适的调度。在整个需求对于第三方框架使用依赖程度不高的情况下,可以考虑在局部自己设计任务调度细节,使用更加轻量级的代码,通过合理的逻辑设计完成简单的耗时网络请求任务的管理。
- 以下,我将通过一个需要设定任务依赖关系的网络耗时任务的多任务管理<对于许多底层开发读者来讲,可以将
任务
理解为一系列封装在一起,具有一定联系的操作即–>特定代码块>[未使用任何第三方框架,全部封装的类库以及c语言代码块都是Xcode提供的基础方案]
- 假设,`任务A`:表示从`url_A`处通过网络请求下载`高清图片1`
`任务B`:表示从`url_B`处通过网络请求下载`高清图片2`
`任务C`:表示依赖于`任务A`和`任务B`完成之后,将图片1——图片2 合成一张新的图片展示
- 在进行设计任务实现之前,先了解一下,苹果官方给出的两种管理任务的API的对比:
- 其中一种是项目部署中广泛被程序员使用的轻量级纯C语言的API,即GCD是,从本质上来讲GCD出现时间早,而且结合block代码块可以非常容易完成任务封装,使用非常广泛。另外一种就是基于GCD封装的高级操作队列即NSOperation 后者是则是Object-C的对象。
- 在GCD中,任务用块(block)来表示,而块是个轻量级的数据结构,更加轻量级则意味着更高效的性能;相反操作队列中的『操作』NSOperation则是个更加重量级的Object-C对象。
但是,相对于GCD后者居上的操作队列NSOperation和NSOperationQueue却有着非常多的GCD无法匹及的优点:
- NSOperationQueue可以方便的调用cancel方法来取消某个操作,而GCD中的任务是无法被取消的(安排好任务之后就不管了)。
- NSOperation可以方便的指定操作间的依赖关系。
- NSOperation可以通过KVO提供对NSOperation对象的精细控制(如监听当前操作的进度或者是否被取消或是否已经完成等)
- NSOperation可以方便的指定操作优先级。操作优先级表示此操作与队列中其它操作之间的优先关系,优先级高的操作先执行,优先级低的后执行。
- 通过自定义NSOperation的子类可以实现操作重用
- 但是,在具体该使用GCD还是使用NSOperation需要看具体的情况
以下是本文开篇的任务需求的简单设计实例
- 开子线程下载图片
//1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.使用简便方法封装操作并添加到队列中
[queue addOperationWithBlock:^{
//3.在该block中下载图片
NSURL *url = [NSURL URLWithString:@"http://news.51sheyuan.com/uploads/allimg/111001/133442IB-2.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
NSLog(@"下载图片操作--%@",[NSThread currentThread]);
//4.回到主线程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
NSLog(@"刷新UI操作---%@",[NSThread currentThread]);
}];
}];
- 下载多张图片合成综合案例(设置操作依赖)
//02 综合案例
- (void)download2
{
NSLog(@"----");
//1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.封装操作下载图片1
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
//拿到图片数据
self.image1 = [UIImage imageWithData:data];
}];
//3.封装操作下载图片2
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://pic.58pic.com/58pic/13/87/82/27Q58PICYje_1024.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
//拿到图片数据
self.image2 = [UIImage imageWithData:data];
}];
//4.合成图片
NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
//4.1 开启图形上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//4.2 画image1
[self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
//4.3 画image2
[self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
//4.4 根据图形上下文拿到图片数据
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// NSLog(@"%@",image);
//4.5 关闭图形上下文
UIGraphicsEndImageContext();
//7.回到主线程刷新UI
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
self.imageView.image = image;
NSLog(@"刷新UI---%@",[NSThread currentThread]);
}];
}];
//5.设置操作依赖
[combine addDependency:op1];
[combine addDependency:op2];
//6.添加操作到队列中执行
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:combine];
}
但是 GCD在开发中同样是有使用的场景的,在以下场景中,综合考虑代码整体效率,可以适当使用更加轻量级的GCD代码块来完成主/非主线程之间的转场切换,以及一些必要的系统延时等等。
//0.获取一个全局的队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//1.先开启一个线程,把下载图片的操作放在子线程中处理
dispatch_async(queue, ^{
//2.下载图片
NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
NSLog(@"下载操作所在的线程--%@",[NSThread currentThread]);
//3.回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
//打印查看当前线程
NSLog(@"刷新UI---%@",[NSThread currentThread]);
});
});
其他常用函数
栅栏函数(控制任务的执行顺序)
dispatch_barrier_async(queue, ^{
NSLog(@"--dispatch_barrier_async-");
});
延迟执行(延迟·控制在哪个线程执行)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"---%@",[NSThread currentThread]);
});
一次性代码(注意不能放到懒加载)
-(void)once
{
//整个程序运行过程中只会执行一次
//onceToken用来记录该部分的代码是否被执行过
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-----");
});
}
快速迭代(开多个线程并发完成迭代操作)
dispatch_apply(subpaths.count, queue, ^(size_t index) {
});
队列组(同栅栏函数)
//创建队列组
dispatch_group_t group = dispatch_group_create();
//队列组中的任务执行完毕之后,执行该函数
dispatch_group_notify(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
进入群组和离开群组
dispatch_group_enter(group);//执行该函数后,后面异步执行的block会被gruop监听
dispatch_group_leave(group);//异步block中,所有的任务都执行完毕,最后离开群组
//注意:dispatch_group_enter|dispatch_group_leave必须成对使用