iOS开发 - 多线程实现方案之GCD篇

GCD概念

GCD为Grand Central Dispatch的缩写,纯c语言编写,是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。详细见百度百科

GCD优点

  1. GCD是苹果公司为多核的并行运算提出的解决方案
  2. GCD会自动利用更多的CPU内核(比如双核、四核)
  3. GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  4. 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

GCD核心

  1. 任务:执行什么操作
  2. 队列:用来存放任务

将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行

注意:任务的取出遵循队列的FIFO原则:先进先出,后进后出

GCD执行任务的两种方式比较 

1.同步函数:

dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

只能在当前线程执行任务,不具备开启新线程的能力

2.异步函数:

dispatch_async(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

在新的线程中执行任务,具备开启新线程的能力

GCD队列的两大类型

1.并发队列(Concurrent Dispatch Queue):

可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效

    //1.创建并发队列
    /**
     第一个参数:c语言字符串,标签
     第二个参数:队列的类型
               DISPATCH_QUEUE_CONCURRENT  并发
               DISPATCH_QUEUE_SERIAL      串行
     */
    dispatch_queue_t creatQueue = dispatch_queue_create("funky", DISPATCH_QUEUE_CONCURRENT);
    
    
    //2.获取全局的并发队列
    /**
     第一个参数:优先级  选择默认的优先级
     第二个参数:留给以后用的  暂时传0
     */
    dispatch_queue_t getQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
获得并发队列的方式

2.串行队列(Serial Dispatch Queue):

让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

    //1.创建串行队列
    dispatch_queue_t creatQueue = dispatch_queue_create("funky", DISPATCH_QUEUE_SERIAL);
    //dispatch_release(creatQueue); // 非ARC需要释放手动创建的队列
    
    
    //2.获取全局的串行队列主队列 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
    dispatch_queue_t getQueue = dispatch_get_main_queue();
获得串行队列的方式

GCD的几种执行任务的方式

//异步函数 + 并发队列 :会开启多条线程,队列中的任务是并发(同时)执行,开启的线程条数是由系统内部决定的
- (void)asyncConcurrent {
    
    dispatch_queue_t queue = dispatch_queue_create("funky", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
       //执行任务
    });
    
}

//异步函数 + 串行队列 :会开启一条线程,队列中的任务是串行(一条执行完在执行下一个)
- (void)asyncSerial {
    
    dispatch_queue_t queue = dispatch_queue_create("funky", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        //执行任务
    });
    
}

//同步函数 + 并发队列 :不会开启线程,任务是串行执行的
- (void)syncConcurrent {
    
    dispatch_queue_t queue = dispatch_queue_create("funky", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        //执行任务
    });
    
}

//同步函数 + 串行队列 :不会开启线程,任务是串行执行的
- (void)syncSerial {
    
    dispatch_queue_t queue = dispatch_queue_create("funky", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        //执行任务
    });
    
}

//异步函数 + 主队列 :不会开启线程,在主线程中,任务是串行执行的
- (void)asyncMain {
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        //执行任务
    });
    
}

//同步函数 + 主队列 :死锁
- (void)syncMain {
    
    /**
     分析:若在主线程中执行此方法,首先获得到主队列,然后发现是同步函数,(封装任务,把任务添加到队列中)
          队列安排主线程来执行任务,但当前主线程在等待方法执行完毕,这样就会形成死锁
     
     主队列特点:如果主队列发现当前主线程有任务在执行,那么主队列会暂停调用队列中的任务,直到线程空闲为止
     
     如果此方法在子线程中调用,则不会形成死锁
     */
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        //执行任务
    });
    
}
View Code

GCD线程间的通信

//创建子线程下载图片
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1489947239211&di=712ed19abb4549e3752acb70fbecc29e&imgtype=0&src=http%3A%2F%2Fe.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F314e251f95cad1c8037ed8c97b3e6709c83d5112.jpg"];
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        UIImage *image = [UIImage imageWithData:data];
        
        //在主线程中更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
           
            self.imageView.image = image;
            
        });
        
    });
下载网络图片

GCD的常见用法 

1.延迟执行

   //延迟执行方法一
    /**
     说明:此方法在子线程中,并不会调用SEL方法,原因是afterDelay方式是使用当前线程的定时器在一定时间后调用SEL,而子线程中默认是没有定时器的
     解决:1、开启线程的定时器  [[NSRunLoop currentRunLoop] run];  
          2、使用dispatch_after来执行定时任务
     */
    [self performSelector:@selector(test) withObject:nil afterDelay:2.0];
    
    
    //延迟执行方法二
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];
    
    //延迟执行方法三
    //参数一:DISPATCH_TIME_NOW 从现在开始计算时间
    //参数二:delayInSeconds 延迟的时间 GCD时间单位:纳秒
    //参数三:队列 (可以控制延迟执行在什么线程下执行)
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
        //执行内容
    });
延迟执行的几种方式

2.一次性代码(单例中的使用)

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        // 只执行1次的代码(这里面默认是线程安全的)
        // 整个APP生命周期中只会执行一次
    });
View Code

3.队列组的运用场景:分别异步执行2个耗时的操作,等2个异步操作都执行完毕后,再回到主线程执行操作

dispatch_group_t group =  dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 执行1个耗时的异步操作

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 执行1个耗时的异步操作

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    // 等前面的异步操作都执行完毕后,回到主线程...

});
队列组

4.栅栏函数

    //栅栏函数不能使用全局并发队列
    //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t queue = dispatch_queue_create("funky", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        //任务1
        NSLog(@"download1--------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        //任务2
        NSLog(@"download2--------%@",[NSThread currentThread]);
    });
    
    //栅栏函数 : 可以让任务1和任务2执行完毕以后在执行栅栏函数以后的其他任务
    dispatch_barrier_async(queue, ^{
        NSLog(@"++++++++++ 这就是个栅栏 ++++++++");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3--------%@",[NSThread currentThread]);
    });
View Code

5.快速迭代(遍历)

    //普通遍历方式
    for (int i = 0; i < 10; i++) {
        NSLog(@"%d------%@",i,[NSThread currentThread]);
    }
    
    /**
     参数1:遍历的次数
     参数2:队列(并发队列)
     参数3:index 索引
     */
    
    //开子线程和主线程一起完成遍历任务,任务的遍历是并发的
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"%zu------%@",index,[NSThread currentThread]);
    });
遍历

6.用函数的方式

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //用函数的方式来封装任务
    /**
     参数一:队列
     参数二:参数
     参数三:要调用的函数
     */
    dispatch_async_f(dispatch_get_global_queue(0, 0), NULL, task);
    
}

void task(void *param){
    //执行耗时操作...
}
dispatch_async_f 简单使用

7.几种定时器的使用

-(void)addTimer1 {
    
    //NSTimer
    
    //方式1
    /*
     参数一:触发时间,单位秒
     参数二:定时起触发对象
     参数三:定时器响应方法
     参数四:用户信息
     参数五:是否重复执行,YES 每个指定的时间重复执行,NO 只执行一次
     */
    //会自动将创建的定时器以默认Mode添加到当前线程runloop中,无需手动添加
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
    
    //立即执行
    [timer fire];
    
    //销毁定时器(销毁后不能重新开启)
    [timer invalidate];
    
    //关闭定时器
    [timer setFireDate:[NSDate distantFuture]];
    
    //开启定时器
    [timer setFireDate:[NSDate distantPast]];
    
    
    
    //方式2
    NSTimer *timer2 = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
    // 将定时器添加到runloop中,否则定时器不会启动
    [[NSRunLoop mainRunLoop] addTimer:timer2 forMode:NSRunLoopCommonModes];
    
    
}
NSTimer
-(void)addTimer2 {
    
    // 创建displayLink
    /*
     当把CADisplayLink对象add到runloop中后,selector就能被周期性调用,类似于重复的NSTimer被启动了;执行invalidate操作时,CADisplayLink对象就会从runloop中移除,selector调用也随即停止,类似于NSTimer的invalidate方法
     
     CADisplayLink是一个和屏幕刷新率同步的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息,CADisplayLink类对应的selector就会被调用一次,所以可以使用CADisplayLink做一些和屏幕操作相关的操作。
     
     重要属性:
     
      frameInterval : NSInteger类型的值,用来设置间隔多少帧调用一次selector方法,默认值是1,即每帧都调用一次。
     
      duration : readOnly的CFTimeInterval值,表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在target的selector被首次调用以后才会被赋值。selector的调用间隔时间计算方式是:调用间隔时间 = duration × frameInterval。
     */
    CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)];
    
    // 将创建的displaylink添加到runloop中,否则定时器不会执行
    [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    
    
    //关闭定时器
    displayLink.paused = YES;
    
    //开启定时器
    displayLink.paused = NO;
    
    // 销毁定时器
    [displayLink invalidate];
    displayLink = nil;
    
    
}
CADisplayLink
@property (nonatomic,strong) dispatch_source_t timer;

-(void)addTimer3 {
    
    //GCD定时器不会受RunLoop的影响,并且是绝对精准的
    
    //1.创建GCD中的定时器
    /*
     参数一:source的类型 DISPATCH_SOURCE_TYPE_TIMER 表示定时器
     参数二:描述信息
     参数三:更详细的描述信息
     参数四:队列,决定GCD定时器中的任务在哪个线程中执行
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    
    //2.设置定时器(起始时间|间隔时间|精准度)
    /*
     参数一:定时器对象
     参数二:起始时间,DISPATCH_TIME_NOW 从现在开始计时
     参数三:间隔时间 2.0 GCD时间单位是 纳秒
     参数四:精准度 绝对精准0
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    
    //3.设置定时器执行的任务
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"--------%@",[NSThread currentThread]);
    });
    
    //4.启动执行
    dispatch_resume(timer);
    
    
    //timer 是一个局部变量,2s之后定时器变量可能会被释放了,所以定时器不工作,为了保证不被释放
    self.timer = timer;

    
    
    /*
         //暂停定时器
         dispatch_suspend(self.timer);
         //开启定时器
         dispatch_resume(self.timer);
         //销毁定时器
         dispatch_cancel(self.timer);
     */
    
}
GCD定时器

 

  

参考文章: http://www.cnblogs.com/wendingding/p/3806821.html 

 

posted @ 2017-03-20 00:35  Funky、  阅读(272)  评论(0)    收藏  举报