美文网首页iOS开发
iOS-GCD从认识到高深莫测

iOS-GCD从认识到高深莫测

作者: 树下敲代码的超人 | 来源:发表于2017-11-23 20:56 被阅读55次

    技 术 文 章 / 超 人


    GCD 介绍
    Grand Central Dispatch(GCD) 是Apple开发的一个多核编程的较新的解决方法,可译为“牛逼的中枢调度器”纯C语言,提供了非常多强大的函数。它主要用于优化应用程序以支持多核处理器以及其他对称处理系统,GCD会自动利用更多的CPU内核(比如双核、四核)。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用,iOS7时能开启5、6条子线程, iOS8以后可以50、60条子线程。

    GCD的优势

    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)所以只需要程序员决定创建什么类型的GCD队列和执行什么任务即可,其他由系统自行管理。
    • 任务的取出遵循FIFO模式:先进先出,后进后出
    • GCD定时器不受RunLoop约束,比NSTimer更加准时
    • 可操作可控性强,几乎没有GCD做不了的事情。

    GCD的组成
    队列(queue)+ 任务(block里执行的操作)

    队列的组成
    是否开启线程(同步,异步) + 任务执行的方式(主队列,串行队列,并发队列,全局并行队列)

    • 同步(dispatch_sync):只能在当前线程执行任务,不具备开启新线程能力。
    • 异步(dispatch_async):可以在新线程执行任务,具备开启新线程能力。
    • 串行队列:按照任务加入队列的顺序,依次执行,只有前一个任务执行完毕,才会执行下一个任务。(推荐结合 异步使用)
    /*
    参数1:队列名称,同一个名称获取到的队列是同一个
    队列类型:
    DISPATCH_QUEUE_SERIAL:串行
    DISPATCH_QUEUE_CONCURRENT:并行
    */
    dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    
    • 并行队列:会先把所有任务放入队列中,当任务添加完毕后,同时执行所有任务。无法确定那个任务先完成。(推荐结合 异步使用)
    dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    • 主队列dispatch_get_main_queue:GCD自带的特殊队列,所有放在主队列里执行的任务,都会在主线程执行
    dispatch_queue_t queue= dispatch_get_main_queue();
    
    • 全局队列 dispatch_get_global_queue:GCD自带的全局并行队列,不需要自己去创建队列。直接使用dispatch_get_global_queue来获取,都是同一个队列,非主线程。不要与 barrier 栅栏方法搭配使用, barrier 只有与自定义的并行队列一起使用,才能让 barrier 达到我们所期望的栅栏功能。与 串行队列或者 global 队列 一起使用,barrier 的表现会和 dispatch_sync 方法一样。
    /*
    (里面的参数第一个为优先级,可以默认为固定写法)
    全局并发队列的优先级(不论是串行还是并行,创建出来的队列默认都是默认优先级。可以使用set_target_queue来改变优先级)
    #define DISPATCH_QUEUE_PRIORITY_HIGH 2  高
    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0  默认(中)
    #define DISPATCH_QUEUE_PRIORITY_LOW (-2)  低
    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  后台 
    */
    dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    组合效果

    同步+串行 :由于同步没有开启新线程的能力,所以会在主线程执行任务,由于串行,会按照任务加入顺序执行任务,上一个执行完,下一个才执行

    /* 创建串行队列 */
    dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    /* 使用同步把任务加入到串行队列中, 第二个参数不用管统一填写0, 我也不知道为什么,哈哈 */
    dispatch_sync(queue,0){
        //执行的任务
        NSLog(@"1------%@",[NSThread currentThread]);
    };
    

    异步+串行 :由于异步,会开启新线程,所以会在子线程执行,由于串行只会开启一个子线程。依次执行

    /* 创建串行队列 */
    dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    /* 使用异步把任务加入到串行队列中*/
    dispatch_async(queue,0){
        //执行的任务
        NSLog(@"1------%@",[NSThread currentThread]);
    };
    

    同步+并行 :由于并行需要同时执行所有任务,但又是同步,不会开启新线程去同时执行所有任务,会在主队列执行。所以同步+并行会按照串行执行,会先把所有任务加入主队列在主线程中一个一个任务执行,但是任务顺序无法确定。

    dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    /* 使用同步把任务加入到并行队列中*/
    dispatch_sync(queue,0){
        //执行的任务
        NSLog(@"1------%@",[NSThread currentThread]);
    };
    

    异步+并行:由于异步会开启一个新的线程,由于并行会同时执行所有任务,有多少任务就会开启多少线程(也不全是,有时候主线程空闲时也会执行)。

    dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    /* 使用异步把任务加入到并行队列中*/
    dispatch_async(queue,0){
        //执行的任务
        NSLog(@"1------%@",[NSThread currentThread]);
    };
    

    全局队列+同步或+异步:全局队列就是并行模式。所以就不说流程了。全局并发队列在整个应用程序中本身是默认存在的,并且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,我们只是选择其中的一个直接拿来用。而Crearte函数是实打实的从头开始去创建一个队列。在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在和使用create函数自己的创建的并发队列一起使用的时候才有效(没有给出具体原因

    同步+主队列:同步不会开启线程所以会在主线程,主队列只会在主线程执行。
    注意:同步任务有一个特性,只要一添加到队列中就要马上执行,主队列中永远就只要一条线程——主线程,此时主线程在等待着主队列调度同步任务,而主队列发现主线程上还有任务未执行完,就不会让同步任务添加到主线程上,由此就造成了互相等待(主队列在等待主线程执行完已有的任务,而主线程又在等待主队列调度同步任务!此时也就是所谓的死锁了!)

    dispatch_sync(dispatch_get_main_queue(),0){
        //执行任务
    };
    

    异步+主队列:异步具有开启子线程能力,但主队列中只有一个主线程,所以任务会在主线程中按照顺序一个一个执行。类似与同步+串行

    dispatch_async(dispatch_get_main_queue(),0){
        //执行任务
    };
    

    暂停或取消队列:GCD中的队列也是可以暂停和恢复的,直接把相应的队列作为参数就传递就可以。使用 dispatch_resume(queue1);和dispatch_suspend(queue1);


    栅栏函数
    • 作用:只有当栅栏函数执行完毕后才能执行后面的函数
    • 需求:使用栅栏函数规定线程执行顺序
    • 注意点:栅栏函数不能使用全局并发队列
    // 栅栏函数不能使用全局并发队列
      /* 创建并发队列 */
     dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
        // 2. 异步函数
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i< 100; i++) {
                NSLog(@"queue1 ----%zd----- %@",i,[NSThread currentThread]);
            }
            
        });
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i< 100; i++) {
                NSLog(@"queue2 ----%zd----- %@",i,[NSThread currentThread]);
            }
        });
        
        
        // 栅栏函数,因为队列是并发的,会等所有任务都添加到队列了才会统一一起执行,但是由于加入栅栏函数,所以会先把上面2个异步任务并行执行,栅栏函数前的任务执行完后,执行栅栏函数,最后才会执行加栅栏函数后面的异步任务。
        //注意:任务都是一起加入到队列中的,并不是执行完栅栏函数后才加入栅栏函数后的队列
        dispatch_barrier_async(queue, ^{
            NSLog(@"栅栏----------");
        });
        
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i< 100; i++) {
                NSLog(@"queue3 ----%zd----- %@",i,[NSThread currentThread]);
            }
        });
    

    队列组(Dispatch Queue)

    dispatch_group_t:队列是用来管理队列里任务的执行方式,队列组则是用来管理队列执行任务的

    • 创建队列组
    • 创建队列
    • 使用队列组异步函数来封装任务, 然后提交到队列中
    • 把当前所有任务执行的情况, 都纳入到队列组监听的范围中
      实例代码:
    // 1. 创建队列组
     dispatch_group_t group = dispatch_group_create();
    
     // 2. 创建并发队列
     dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
    
     // 3. 使用异步函数组添加任务
     dispatch_group_async(group, queue, ^{
         NSLog(@"1---%@", [NSThread currentThread]);
     });
    
     dispatch_group_async(group, queue, ^{
         NSLog(@"2---%@", [NSThread currentThread]);
     });
    
     // 4. 让队列组监听任务的完成
     dispatch_group_notify(group, queue, ^{
        //当队列组group管理的queue中所有任务执行完毕后,就会在该函数中收到回调。类似与栅栏,如果后面还有任务,则必须等上面任务完成后且dispatch_group_notify任务执行完毕后才会将后面等任务加入队列组中等队列
         NSLog(@"执行完毕");
     });
    

    队列组+函数 :只能使用异步

    • 首先要使用队列组创建函数, 创建一个队列组
    • 创建一个并发队列
    • 开启队列组, 然后使用异步函数封装每一个任务
    • 当任务执行结束之后, 要将加入到队列组的任务移除队列组(类似于引用计数管理, 计数+1必须伴随计数-1) + 队列组通常与dispatch_group_wait连用, 等待队列组中的任务X秒, 之后再继续执行后面的任务
          // 1. 获取队列组
          dispatch_group_t group = dispatch_group_create();
          // 2. 创建全局并行队列
          dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
          // 3. 开启队列组,将下面放入queue队列的异步任务放入队列组中
          dispatch_group_enter(group);
          // 4. 使用异步函数封装任务
          dispatch_async(queue, ^{
              NSLog(@"1---%@", [NSThread currentThread]);
              // 5. 确保任务结束之后,让该任务离开队列组
              dispatch_group_leave(group);
          });
    
          dispatch_group_enter(group);
          dispatch_async(queue, ^{
              NSLog(@"2---%@", [NSThread currentThread]);
              dispatch_group_leave(group);
          });
    
       // 拦截通知
      //    dispatch_group_notify(group, queue, ^{
      //        NSLog(@"任务结束");
      //    });
    
          // 同步执行,等待队列组time的时间后执行后面的代码,如果队列组中的任务都完成了,就返回0,如果没完成,就返回非0值
          dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 0.00000002 * NSEC_PER_SEC);
    
          // 等待tiemr秒的时间,不管队列中任务是否完成,都继续往下执行,如果改时间内任务完成了就返回0,否则是非零值
          long n = dispatch_group_wait(group, timer);
          NSLog(@"%ld", n);
    

    Dispatch Source (信号源)

    GCD中除了主要的 Dispatch Queue 外,还有不太引人注目的 Dispatch Source .它是BSD系内核惯有功能kqueue的包装。kqueue 是在 XNU 内核中发生各种事件时,在应用程序编程方执行处理的技术。其 CPU 负荷非常小,尽量不占用资源。kqueue 可以说是应用程序处理 XNU 内核中发生的各种事件的方法中最优秀的一种。

    Dispatch Source 也使用在了 Core Foundation 框架的用于异步网络的API CFSocket 中。因为Foundation 框架的异步网络 API 是通过CFSocket实现的,所以可享受到仅使用 Foundation 框架的 Dispatch Source 带来的好处。

    那么优势何在?使用的 Dispatch Source 而不使用 dispatch_async 的唯一原因就是利用联结的优势。

    联结的大致流程:在任一线程上调用它的的一个函数 dispatch_source_merge_data 后,会执行 Dispatch Source 事先定义好的句柄(可以把句柄简单理解为一个 block )。

    这个过程叫 Custom event。是 dispatch source 支持处理的一种事件。

    简单地说,一个监视某些类型事件的对象。当这些事件发生时,它自动将定义好的block放入一个dispatch queue的执行例程中。这种事件是由你调用 dispatch_source_merge_data 函数来向自己发出的信号。

    dispatch_source_t add = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0,
                                                       dispatch_get_main_queue());
    

    dispatch_source_create参数说明:
    参数:

    参数 意义
    type dispatch源可处理的事件
    handle 可以理解为句柄、索引或id,假如要监听进程,需要传入进程的ID
    mask 可以理解为描述,提供更详细的描述,让它知道具体要监听什么
    queue 自定义源需要的一个队列,用来处理所有的响应句柄(block)

    Dispatch Source可处理的所有事件(type)

    名称 内容
    DISPATCH_SOURCE_TYPE_DATA_ADD 自定义的事件,变量增加
    DISPATCH_SOURCE_TYPE_DATA_OR 自定义的事件,变量OR
    DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送
    DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
    DISPATCH_SOURCE_TYPE_PROC 进程监听,如进程的退出、创建一个或更多的子线程、进程收到UNIX信号
    DISPATCH_SOURCE_TYPE_READ IO操作,如对文件的操作、socket操作的读响应
    DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信号时响应
    DISPATCH_SOURCE_TYPE_TIMER 定时器
    DISPATCH_SOURCE_TYPE_VNODE 文件状态监听,文件被删除、移动、重命名
    DISPATCH_SOURCE_TYPE_WRITE IO操作,如对文件的操作、socket操作的写响应

    注意:

    • DISPATCH_SOURCE_TYPE_DATA_ADD
      当同一时间,一个事件的的触发频率很高,那么Dispatch Source会将这些响应以ADD的方式进行累积,然后等系统空闲时最终处理,如果触发频率比较零散,那么Dispatch Source会将这些事件分别响应。
    • DISPATCH_SOURCE_TYPE_DATA_OR 和上面的一样,是自定义的事件,但是它是以OR的方式进行累积

    信号源使用函数

    dispatch_suspend(queue) //挂起队列
    
    dispatch_resume(source) //分派源创建时默认处于暂停状态,在分派源分派处理程序之前必须先恢复
    
    dispatch_source_merge_data //向分派源发送事件,需要注意的是,不可以传递0值(事件不会被触发),同样也不可以传递负数。
    
    dispatch_source_set_event_handler //设置响应分派源事件的block,在分派源指定的队列上运行
    
    dispatch_source_get_data //得到分派源的数据
    
    uintptr_t dispatch_source_get_handle(dispatch_source_t source); //得到dispatch源创建,即调用dispatch_source_create的第二个参数
    
    unsigned long dispatch_source_get_mask(dispatch_source_t source); //得到dispatch源创建,即调用dispatch_source_create的第三个参数
    
    void dispatch_source_cancel(dispatch_source_t source); //取消dispatch源的事件处理--即不再调用block。如果调用dispatch_suspend只是暂停dispatch源。
    
    long dispatch_source_testcancel(dispatch_source_t source); //检测是否dispatch源被取消,如果返回非0值则表明dispatch源已经被取消
    
    void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler); //dispatch源取消时调用的block,一般用于关闭文件或socket等,释放相关资源
    
    void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler); //可用于设置dispatch源启动时调用block,调用完成后即释放这个block。也可在dispatch源运行当中随时调用这个函数。
    

    GCD延迟操作

    /*
    DISPATCH_TIME_NOW:现在开始的意
    2.0 * NSEC_PER_SEC:设置的秒数
    dispatch_get_main_queue():主队列的意思
    */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        2秒后执行这里的代码... 在哪个线程执行,跟队列类型有关  
    });
    

    一次性代码
    使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次,如果把onceToken设置成全局变量,可以控制onceToken的值来控制代码再次执行

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
         //程序运行过程中,永远只执行1次的代码(这里面默认是线程安全的)
    });
    

    重复多次执行相同任务

    /* 获取全局队列 */
    dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
        //使用dispatch_apply()函数控制提交的任务代码块执行5次,该函数所需的代码块可以带一个参数,这个参数表示当前正在执行第几次  
        dispatch_apply(5,queue,^(size_t time){  
            NSLog(@"---执行%lu次---%@",time,[NSThread currentThread]);  
        });
    

    GCD定时器

    0.创建全局队列,由于定时任务,每次都只有一个任务,所以串行并行无所谓
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
     
    1.创建一个GCD的定时器
     
     第一个参数:说明这是一个定时器
     第四个参数:GCD的回调任务添加到那个队列中执行,如果是主队列则在主线程执行
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    2.设置定时器的开始时间,间隔时间以及精准度
     
    设置开始时间,三秒钟之后调用
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW,3.0 *NSEC_PER_SEC);
    设置定时器工作的间隔时间
    uint64_t intevel = 1.0 * NSEC_PER_SEC;
     
     第一个参数:要给哪个定时器设置
     第二个参数:定时器的开始时间DISPATCH_TIME_NOW表示从当前开始
     第三个参数:定时器调用方法的间隔时间
     第四个参数:定时器的精准度,如果传0则表示采用最精准的方式计算,如果传大于0的数值,则表示该定时切换i可以接收该值范围内的误差,通常传0
     该参数的意义:可以适当的提高程序的性能
     注意点:GCD定时器中的时间以纳秒为单位(面试)
     
     
    dispatch_source_set_timer(timer, start, intevel, 0 * NSEC_PER_SEC);
     
    3.设置定时器开启后回调的方法
     
     第一个参数:要给哪个定时器设置
     第二个参数:回调block
     
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"------%@",[NSThread currentThread]);
    });
     
    4.执行定时器
    dispatch_resume(timer);
     
    注意:dispatch_source_t本质上是OC类,在这里是个局部变量,需要强引用
    self.timer = timer;
    

    进度条

    //1、指定DISPATCH_SOURCE_TYPE_DATA_ADD,做成Dispatch Source(分派源)。设定Main Dispatch Queue 为追加处理的Dispatch Queue
        dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    
        __block NSUInteger totalComplete = 0;
    
        dispatch_source_set_event_handler(source, ^{
    
            //当处理事件被最终执行时,计算后的数据可以通过dispatch_source_get_data来获取。这个数据的值在每次响应事件执行后会被重置,所以totalComplete的值是最终累积的值。
            NSUInteger value = dispatch_source_get_data(source);
    
            totalComplete += value;
    
            NSLog(@"进度:%@", @((CGFloat)totalComplete/100));
    
            NSLog(@":large_blue_circle:线程号:%@", [NSThread currentThread]);
        });
    
        //分派源创建时默认处于暂停状态,在分派源分派处理程序之前必须先恢复。
        dispatch_resume(source);
    
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
        //2、恢复源后,就可以通过dispatch_source_merge_data向Dispatch Source(分派源)发送事件:
        for (NSUInteger index = 0; index < 100; index++) {
    
            dispatch_async(queue, ^{
    
                dispatch_source_merge_data(source, 1);
    
                NSLog(@":recycle:线程号:%@~~~~~~~~~~~~i = %ld", [NSThread currentThread], index);
    
                usleep(20000);//0.02秒
    
            });
        }
    
        //3、比较上面的for循环代码,将dispatch_async放在外面for循环的外面,打印结果不一样
        //dispatch_async(queue, ^{
        //
        //    for (NSUInteger index = 0; index < 100; index++) {
        //
        //        dispatch_source_merge_data(source, 1);
        //
        //        NSLog(@":recycle:线程号:%@~~~~~~~~~~~~i = %ld", [NSThread currentThread], index);
        //
        //        usleep(20000);//0.02秒
        //    }
        //});
    
    
        //2是将100个任务添加到queue里面,而3是在queue里面添加一个任务,而这一个任务做了100次循环
    

    等待多个网络请求全部完成后执行其他操作
    有时候我们会遇到,请求多个无互相关联的网络请求。如果先等第一个网络请求成功后在执行第二个网络请求,请求事件就是累加等。 而使用信号量可以达到多个网络请求同时发出分别接受,这样多个网络请求所消耗等时间就是请求中耗时最长那一个的时间,而不是累加时间。

    /* 创建信号量 */
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    /* 由于有的时候代码实在主线中执行的,不能用信号量堵塞主线程*/
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
    
    
    /* 网络请求1 */
    [[AFHTTPSessionManager manager] GET:URL parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {  }     
     success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {  
    /* 请求成功,发送一个信号量*/
    dispatch_semaphore_signal(semaphore);
    }
    failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull   error) {  
    /* 请求失败也要发送一个信号量,不然一会值堵塞线程*/
    dispatch_semaphore_signal(semaphore);
    }];
    
    
    
    /* 网络请求2 */
    [[AFHTTPSessionManager manager] GET:URL parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {  }     
     success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {  
    /* 请求成功,发送一个信号量*/
    dispatch_semaphore_signal(semaphore);
    }
    failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull   error) {  
    /* 请求失败也要发送一个信号量,不然一会值堵塞线程*/
    dispatch_semaphore_signal(semaphore);
    }];
    
    
    
    /* 网络请求3 */
    [[AFHTTPSessionManager manager] GET:URL parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {  }     
     success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {  
    /* 请求成功,发送一个信号量*/
    dispatch_semaphore_signal(semaphore);
    }
    failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull   error) {  
    /* 请求失败也要发送一个信号量,不然一会值堵塞线程*/
    dispatch_semaphore_signal(semaphore);
    }];
    /*等待信号,因为有3个网络请求,所以要等待3个信号,这里会堵塞线程,等到3个任务都完成了,才会执行后面都代码*/
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    /* 在主线程中刷新界面*/
    dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"所有网络请求完成了,可以刷新界面了");
            });
    });
    

    (未完待续...)

    相关文章

      网友评论

        本文标题:iOS-GCD从认识到高深莫测

        本文链接:https://www.haomeiwen.com/subject/kyjzvxtx.html