• 在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必须成对使用