巧谈GCD

作者: 天口三水羊 | 来源:发表于2016-06-20 17:37 被阅读11541次

    谈到iOS多线程,一般都会谈到四种方式:pthread、NSThread、GCD和NSOperation。其中,苹果推荐也是我们最经常使用的无疑是GCD。对于身为开发者的我们来说,并发一直都很棘手,如果对GCD的理解不够透彻,那么iOS开发的历程绝对不会顺利。这里,我会从几个角度浅谈我对GCD的理解。

    一、多线程背景

    Although threads have been around for many years and continue to have their uses, they do not solve the general problem of executing multiple tasks in a scalable way. With threads, the burden of creating a scalable solution rests squarely on the shoulders of you, the developer. You have to decide how many threads to create and adjust that number dynamically as system conditions change. Another problem is that your application assumes most of the costs associated with creating and maintaining any threads it uses.

    上述大致说出了直接操纵线程实现多线程的弊端:

    • 开发人员必须根据系统的变化动态调整线程的数量和状态,即对开发者的负担重。
    • 应用程序会在创建和维护线程上消耗很多成本,即效率低。

    相对的,GCD是一套低层级的C API,通过 GCD,开发者只需要向队列中添加一段代码块(block或C函数指针),而不需要直接和线程打交道。GCD在后端管理着一个线程池,它不仅决定着你的代码块将在哪个线程被执行,还根据可用的系统资源对这些线程进行管理。GCD的工作方式,使其拥有很多优点(快、稳、准):

    • 快,更快的内存效率,因为线程栈不暂存于应用内存。
    • 稳,提供了自动的和全面的线程池管理机制,稳定而便捷。
    • 准,提供了直接并且简单的调用接口,使用方便,准确。

    二、队列和任务

    初学GCD的时候,肯定会纠结一些看似很关键但却毫无意义的问题。比如:GCD和线程到底什么关系;异步任务到底在哪个线程工作;队列到底是个什么东西;mian queue和main thread到底搞什么名堂等等。现在,这些我们直接略过(最后拾遗中会谈一下),苹果既然推荐使用GCD,那么为什么还要纠结于线程呢?需要关注的只有两个概念:队列、任务。

    1. 队列

    调度队列是一个对象,它会以first-in、first-out的方式管理您提交的任务。GCD有三种队列类型:

    • 串行队列,串行队列将任务以先进先出(FIFO)的顺序来执行,所以串行队列经常用来做访问某些特定资源的同步处理。你可以也根据需要创建多个队列,而这些队列相对其他队列都是并发执行的。换句话说,如果你创建了4个串行队列,每一个队列在同一时间都只执行一个任务,对这四个任务来说,他们是相互独立且并发执行的。如果需要创建串行队列,一般用dispatch_queue_create这个方法来实现,并指定队列类型DISPATCH_QUEUE_SERIAL。
    • 并行队列,并发队列虽然是能同时执行多个任务,但这些任务仍然是按照先到先执行(FIFO)的顺序来执行的。并发队列会基于系统负载来合适地选择并发执行这些任务。并发队列一般指的就是全局队列(Global queue),进程中存在四个全局队列:高、中(默认)、低、后台四个优先级队列,可以调用dispatch_get_global_queue函数传入优先级来访问队列。当然我们也可以用dispatch_queue_create,并指定队列类型DISPATCH_QUEUE_CONCURRENT,来自己创建一个并发队列。
    • 主队列,与主线程功能相同。实际上,提交至main queue的任务会在主线程中执行。main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。

    额外说一句,上面也说过,队列间的执行是并行的,但是也存在一些限制。比如,并行执行的队列数量受到内核数的限制,无法真正做到大量队列并行执行;比如,对于并行队列中的全局队列而言,其存在优先级关系,执行的时候也会遵循其优先顺序,而不是并行。

    2. 任务

    linux内核中的任务的定义是描述进程的一种结构体,而GCD中的任务只是一个代码块,它可以指一个block或者函数指针。根据这个代码块添加进入队列的方式,将任务分为同步任务和异步任务:

    • 同步任务,使用dispatch_sync将任务加入队列。将同步任务加入串行队列,会顺序执行,一般不这样做并且在一个任务未结束时调起其它同步任务会死锁。将同步任务加入并行队列,会顺序执行,但是也没什么意义。
    • 异步任务,使用dispatch_async将任务加入队列。将异步任务加入串行队列,会顺序执行,并且不会出现死锁问题。将异步任务加入并行队列,会并行执行多个任务,这也是我们最常用的一种方式。

    3. 简单应用

    // 队列的创建,queue1:中(默认)优先级的全局并行队列、queue2:主队列、queue3:未指定type则为串行队列、queue4:指定串行队列、queue5:指定并行队列
    dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t queue2 = dispatch_get_main_queue();
    dispatch_queue_t queue3 = dispatch_queue_create("queue3", NULL);
    dispatch_queue_t queue4 = dispatch_queue_create("queue4", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue5 = dispatch_queue_create("queue5", DISPATCH_QUEUE_CONCURRENT);
    
    // 队列中添加异步任务
    dispatch_async(queue1, ^{
    // 任务
    ...
    });
    
    // 队列中添加同步任务
    dispatch_sync(queue1, ^{
    // 任务
    ...
    });
    

    三、GCD常见用法和应用场景

    非常喜欢一句话:Talk is cheap, show me the code.接下来对GCD的使用,我会通过代码展示。

    1. dispatch_async

    一般用法

    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalQueue, ^{
        // 一个异步的任务,例如网络请求,耗时的文件操作等等
        ...
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI刷新
            ...
        });
    });
    

    应用场景
    这种用法非常常见,比如开启一个异步的网络请求,待数据返回后返回主队列刷新UI;又比如请求图片,待图片返回刷新UI等等。

    2. dispatch_after

    一般用法

    dispatch_queue_t queue= dispatch_get_main_queue();
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
        // 在queue里面延迟执行的一段代码
        ...
    });
    

    应用场景
    这为我们提供了一个简单的延迟执行的方式,比如在view加载结束延迟执行一个动画等等。

    3. dispatch_once

    一般用法

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行一次的任务
        ...
    });
    

    应用场景
    可以使用其创建一个单例,也可以做一些其他只执行一次的代码,比如做一个只能点一次的button(好像没啥用)。

    4. dispatch_group

    一般用法

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        // 异步任务1
    });
    
    dispatch_group_async(group, queue, ^{
        // 异步任务2
    });
    
    // 等待group中多个异步任务执行完毕,做一些事情,介绍两种方式
    
    // 方式1(不好,会卡住当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    ...
    
    // 方式2(比较好)
    dispatch_group_notify(group, mainQueue, ^{
        // 任务完成后,在主队列中做一些操作
        ...
    });
    

    应用场景
    上述的一种方式,可以适用于自己维护的一些异步任务的同步问题;但是对于已经封装好的一些库,比如AFNetworking等,我们不获取其异步任务的队列,这里可以通过一种计数的方式控制任务间同步,下面为解决单界面多接口的一种方式。

    // 两个请求和参数为我项目里面的不用在意。
    
    // 计数+1
    dispatch_group_enter(group);
    [JDApiService getActivityDetailWithActivityId:self.activityId Location:stockAddressId SuccessBlock:^(NSDictionary *userInfo) {
        // 数据返回后一些处理
        ...
    
        // 计数-1
        dispatch_group_leave(group);
    } FailureBlock:^(NSError *error) {
        // 数据返回后一些处理
        ...
    
        // 计数-1
        dispatch_group_leave(group);
    }];
    
    // 计数+1
    dispatch_group_enter(group);
    [JDApiService getAllCommentWithActivityId:self.activityId PageSize:3 PageNum:self.commentCurrentPage SuccessBlock:^(NSDictionary *userInfo) {
        // 数据返回后一些处理
        ...
    
        // 计数-1
        dispatch_group_leave(group);
    } FailureBlock:^(NSError *error) {
        // 数据返回后一些处理
        ...
    
        // 计数-1
        dispatch_group_leave(group);
    }];
    
    // 其实用计数的说法可能不太对,但是就这么理解吧。会在计数为0的时候执行dispatch_group_notify的任务。
    dispatch_group_notify(group, mainQueue, ^{
        // 一般为回主队列刷新UI
        ...
    });
    

    5. dispatch_barrier_async

    一般用法

    // dispatch_barrier_async的作用可以用一个词概括--承上启下,它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。本例中,任务4会在任务1、2、3都执行完之后执行,而任务5、6会等待任务4执行完后执行。
    
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 任务1
        ...
    });
    dispatch_async(queue, ^{
        // 任务2
        ...
    });
    dispatch_async(queue, ^{
        // 任务3
        ...
    });
    dispatch_barrier_async(queue, ^{
        // 任务4
        ...
    });
    dispatch_async(queue, ^{
        // 任务5
        ...
    });
    dispatch_async(queue, ^{
        // 任务6
        ...
    });
    

    应用场景
    和dispatch_group类似,dispatch_barrier也是异步任务间的一种同步方式,可以在比如文件的读写操作时使用,保证读操作的准确性。另外,有一点需要注意,dispatch_barrier_sync和dispatch_barrier_async只在自己创建的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样。

    6. dispatch_apply

    一般用法

    // for循环做一些事情,输出0123456789
    for (int i = 0; i < 10; i ++) {
        NSLog(@"%d", i);
    }
    
    // dispatch_apply替换(当且仅当处理顺序对处理结果无影响环境),输出顺序不定,比如1098673452
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    /*! dispatch_apply函数说明
    *
    *  @brief  dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API
    *         该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束
    *
    *  @param 10    指定重复次数  指定10次
    *  @param queue 追加对象的Dispatch Queue
    *  @param index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block
    *
    */
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu", index);
    });
    

    应用场景
    那么,dispatch_apply有什么用呢,因为dispatch_apply并行的运行机制,效率一般快于for循环的类串行机制(在for一次循环中的处理任务很多时差距比较大)。比如这可以用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性,如果用for循环,耗时较多,并且每个表单的数据没有依赖关系,所以用dispatch_apply比较好。

    7. dispatch_suspend和dispatch_resume

    一般用法

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_suspend(queue); //暂停队列queue
    dispatch_resume(queue);  //恢复队列queue
    

    应用场景
    这种用法我还没有尝试过,不过其中有个需要注意的点。这两个函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但还没有执行的任务不会执行,直到队列被恢复。

    8. dispatch_semaphore_signal

    一般用法

    // dispatch_semaphore_signal有两类用法:a、解决同步问题;b、解决有限资源访问(资源为1,即互斥)问题。
    // dispatch_semaphore_wait,若semaphore计数为0则等待,大于0则使其减1。
    // dispatch_semaphore_signal使semaphore计数加1。
    
    // a、同步问题:输出肯定为1、2、3。
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
    dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
    dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);
    
    dispatch_async(queue, ^{
        // 任务1
        dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
        NSLog(@"1\n");
        dispatch_semaphore_signal(semaphore2);
        dispatch_semaphore_signal(semaphore1);
    });
    
    dispatch_async(queue, ^{
        // 任务2
        dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
        NSLog(@"2\n");
        dispatch_semaphore_signal(semaphore3);
        dispatch_semaphore_signal(semaphore2);
    });
    
    dispatch_async(queue, ^{
        // 任务3
        dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
        NSLog(@"3\n");
        dispatch_semaphore_signal(semaphore3);
    });
    
    // b、有限资源访问问题:for循环看似能创建100个异步任务,实质由于信号限制,最多创建10个异步任务。
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    for (int i = 0; i < 100; i ++) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(queue, ^{
        // 任务
        ...
        dispatch_semaphore_signal(semaphore);
        });
    }
    

    应用场景
    其实关于dispatch_semaphore_t,并没有看到太多应用和资料解释,我只能参照自己对linux信号量的理解写了两个用法,经测试确实相似。这里,就不对一些死锁问题进行讨论了。

    9. dispatch_set_context、dispatch_get_context和dispatch_set_finalizer_f

    一般用法

    // dispatch_set_context、dispatch_get_context是为了向队列中传递上下文context服务的。
    // dispatch_set_finalizer_f相当于dispatch_object_t的析构函数。
    // 因为context的数据不是foundation对象,所以arc不会自动回收,一般在dispatch_set_finalizer_f中手动回收,所以一般讲上述三个方法绑定使用。
    
    - (void)test
    {
        // 几种创建context的方式
        // a、用C语言的malloc创建context数据。
        // b、用C++的new创建类对象。
        // c、用Objective-C的对象,但是要用__bridge等关键字转为Core Foundation对象。
    
        dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
        if (queue) {
            // "123"即为传入的context
            dispatch_set_context(queue, "123");
            dispatch_set_finalizer_f(queue, &xigou);
        }
        dispatch_async(queue, ^{
            char *string = dispatch_get_context(queue);
            NSLog(@"%s", string);
        });
    }
    
    // 该函数会在dispatch_object_t销毁时调用。
    void xigou(void *context)
    {
        // 释放context的内存(对应上述abc)
    
        // a、CFRelease(context);
        // b、free(context);
        // c、delete context;
    }
    
    

    应用场景
    dispatch_set_context可以为队列添加上下文数据,但是因为GCD是C语言接口形式的,所以其context参数类型是“void *”。需使用上述abc三种方式创建context,并且一般结合dispatch_set_finalizer_f使用,回收context内存。

    四、内存和安全

    稍微提一下吧,因为部分人纠结于dispatch的内存问题。
    内存

    • MRC:用dispatch_retain和dispatch_release管理dispatch_object_t内存。
    • ARC:ARC在编译时刻自动管理dispatch_object_t内存,使用retain和release会报错。

    安全
    dispatch_queue是线程安全的,你可以随意往里面添加任务。

    五、拾遗

    这里主要提一下GCD的一些坑和线程的一些问题。

    1. 死锁

    dispatch_sync

    // 假设这段代码执行于主队列
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    // 在主队列添加同步任务
    dispatch_sync(mainQueue, ^{
        // 任务
        ...
    });
    
    // 在串行队列添加同步任务 
    dispatch_sync(serialQueue, ^{
        // 任务
        ...
        dispatch_sync(serialQueue, ^{
            // 任务
            ...
        });
    };
    

    dispatch_apply

    // 因为dispatch_apply会卡住当前线程,内部的dispatch_apply会等待外部,外部的等待内部,所以死锁。
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(10, queue, ^(size_t) {
        // 任务
        ...
        dispatch_apply(10, queue, ^(size_t) {
            // 任务
            ...
        });
    });
    

    dispatch_barrier
    dispatch_barrier_sync在串行队列和全局并行队列里面和dispatch_sync同样的效果,所以需考虑同dispatch_sync一样的死锁问题。

    2. dispatch_time_t

    // dispatch_time_t一般在dispatch_after和dispatch_group_wait等方法里作为参数使用。这里最需要注意的是一些宏的含义。
    // NSEC_PER_SEC,每秒有多少纳秒。
    // USEC_PER_SEC,每秒有多少毫秒。
    // NSEC_PER_USEC,每毫秒有多少纳秒。
    // DISPATCH_TIME_NOW 从现在开始
    // DISPATCH_TIME_FOREVE 永久
    
    // time为1s的写法
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
    

    3. GCD和线程的关系

    如果你是新手,GCD和线程暂时木有关系。
    如果你是高手,我们做朋友吧。

    六、参考文献

    1、https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW2
    2、https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/
    3、http://tutuge.me/2015/04/03/something-about-gcd/
    4、http://www.jianshu.com/p/85b75c7a6286
    5、http://www.jianshu.com/p/d56064507fb8

    相关文章

      网友评论

      • minjing_lin:Talk is cheap, show me the code. 记着了,哈哈
      • 拧发条鸟xds:作者,死锁的dispatch_apply情况不对,并发队列应该改成串行队列,并发队列不会造成死锁。
      • 金蛇郎君木子肆:就喜欢这样的文章,务实!!!
      • Lefe:你好,如果假如有三个异步任务,任务1、任务2、任务3。如果使用GCD的话,如何能让任务顺序执行,任务1完成后执行任务2,最后执行任务3且不卡住主线程,谢谢
        天口三水羊:@Lefe 可以的 你试试 我手头没电脑
        Lefe:@天口三水羊 使用barrier,同步任务可以,异步任务不可以吧?
        天口三水羊:@Lefe 能啊 dispatch barrier 信号量 都能啊
      • 上帝很忙:同步任务用到的地方,讲一下。
      • zcz:谢谢分享!
      • hejiyang:很全面
        天口三水羊:@hejiyang :smile:
      • 迟明子:单页面多接口,B接口获取数据的参数又依赖A接口的时候,只能在A接口调用方法的block中调用B接口.看完lz说的这种
        dispatch_group_notify(group, mainQueue, ^{...
        类似于计数器的方式的调用方式,感觉有很大的实用价值.
        感谢作者大大!!!
      • 小怡情ifelse:非常详细的GCD博文 谢谢撸主分享:sunglasses:
      • 顺其自然JX:大神 这句话"并行队列,并发队列虽然是能同时执行多个任务,但这些任务仍然是按照先到先执行(FIFO)的顺序来执行的"我不理解,,,,fifo顺序不就不能同时吗?怎么还能同时执行多个任务
        天口三水羊:@顺其自然jx 举个例子 比如在并行队列里面添加同步任务 肯定是先到先执行 而异步任务的先后执行没有什么意义 应该关注的是异步任务完成的时间点
      • coolLee:面试够用啦,啊哈哈。。。
        天口三水羊:@coolLee 好吧:smile:但这不妨碍你给来个喜欢来个关注:kissing_heart:
        coolLee:@天口三水羊 大兄弟,感觉开发几乎用不到啊 :joy_cat:
        天口三水羊:@coolLee 开发也够用了 还不关注喜欢一波吗
      • 卟师:我能转载分享吗?我会标注上作者和出处的
        天口三水羊:@卟师 可以的哦 给个简书链接就好 开心的话 给个关注和喜欢:smile:
      • 开发全靠xib:有一句话感觉说的不是很准确。“其存在优先级,任务会遵守优先级.”应该是优先级高的会获得更多的执行时间.优先级高的是不是就会先执行,这个也不是一定的.如有错误请轻拍
        天口三水羊:@纳尼叫啥 好的 测试完了来和我说一声哦:smile:
        开发全靠xib:@天口三水羊 我记得大部分情况都是优先级高的先执行,然后优先级低的和高的穿插执行,但是优先级高的被执行的几率更大.我回去写写确认一下
        天口三水羊:@纳尼叫啥 按我的理解 系统提供的高优先级队列一定会早于低优先级队列执行:scream::scream:
      • L泽:吴淼阳
        天口三水羊:@L大帝 :scream::scream::scream:
      • 九林:棒棒搭
      • 叶舞清风:某学习了
        天口三水羊:@叶舞清风 :smile:
      • FinnZ:整理得很好
      • 343cea0f7107: 坐等其他总结帖
      • Aliv丶Zz:找的就是这个! 😊😊😊
        天口三水羊:@Aliv丶Zz 既然找到了 就来个喜欢来个关注吧
      • 4b269076f838:写的真好,有代码,有应用场景!
      • 抱走_萝莉:已收藏
        天口三水羊:@抱走_萝莉 点个喜欢 点个关注 岂不是更完美
      • 春泥Fu:楼主大大你好,我我想问下,要完成多个异步的任务结束后再执行某些动作,是不是都需要每个异步开始前dispatch_group_enter(group);结束后dispatch_group_leave(group);在所有异步都结束的情况下会执行dispatch_group_notify
        天口三水羊:@春泥Fu 帮到你就好 顺带求关注:stuck_out_tongue_winking_eye:
        春泥Fu:@天口三水羊 :kissing_heart: get,谢谢啦!
        天口三水羊:@春泥Fu 不是的 如果你能获取到任务执行的group的话 那么只需要dispatch_group_notifiy 参照我文中所写的一般样例 使用afn的时候 因为获取不到group 所以只能通过enter、leave这种计数的方式
      • 83a5af33546d:写的很详细,
        天口三水羊:@开了个发 :grin:
      • R0b1n_L33:有一个问题说一下
        MSEC = Millisecond 毫秒
        USEC = Microsecond 微秒

        所以上面dispatch_time的部分
        USEC_PER_SEC 是代表1秒有1000,000微秒
        天口三水羊:@ljysdfz 好的 我测试下 如果错误 马上修改
      • R0b1n_L33:写的好 例子都切中要害
        天口三水羊:@ljysdfz 谢谢支持 能帮到你那是极好的
      • 195aa2622981:问个问题,我想写一个扫描文件程序,并同时显示已经扫描到的文件数量,用gcd没有达到使用效果,不知道为啥
        queue.async {
        self.queue.async {
        self.fileCount.stringValue = "\(self.index)"

        }
        self.findFile(dir: dir)
        }
        index是个全局计数变量
        195aa2622981:@战栗 fileCount是一个label控件
        195aa2622981:点击按钮的动作就这些 self.findFile是扫描指定目录下的文件和文件夹,并递归扫描到的文件夹,保存扫描到的文件到一个字典里并且index+1
        哦对了,初始化gcd是:let queue=DispatchQueue.global()
        天口三水羊:@战栗 从这些代码里面看不出来什么哦 多上点代码 骚年
      • 195aa2622981:感谢
        天口三水羊:@战栗 客气 求关注
      • minjing_lin:果断收藏了
        天口三水羊:@MinJing_Lin 也赞一下 关注一波 支持作者一下哦
      • 鋼鉄侠:用NSOperation会有多少差异?
        天口三水羊:@万少 问题没太听懂:fearful:
        万少:楼主好 我用dispatch_group_t 我单个页面有两个接口 优势两个接口的数据都打印出来 有时只打印出一个 还有时两个都不打印 我打断点数据获取方法都走 这是什么原因 :blush:
        天口三水羊:@鋼鉄侠 后面我会写一下nsoperation:stuck_out_tongue_winking_eye:
      • Rchongg:https://segmentfault.com/q/1010000002767845 看看这个!!!
        Rchongg:@Rchongg 解决多个请求 统一到达
        天口三水羊:我看了下 不是被叶孤城解决了吗?
        天口三水羊:@Rchongg 怎么啦 有什么问题哦
      • 天蚕青春豆:很不错
        天口三水羊:@天蚕青春豆 谢谢支持
      • 石头撞地球: :kissing_heart:
        天口三水羊:@不磨人的小妖精 :heart_eyes:
        天口三水羊:@不磨人的小妖精 :grin:
      • ROOKIE:深入浅出 很实用
      • codexu:我还以为 共 产 党 呢
      • jkhmnk:GCD............................
        天口三水羊:@jkhmnk 666:scream:
        jkhmnk:@天口三水羊 我以为是土共呢
        天口三水羊:@jkhmnk 怎么了:scream:
      • Gavin008:赞
        天口三水羊:@Gavin008 谢谢支持
      • mingmingsky:不错的总结,不过新手表示很少用到gcd,可能用的都是框架的原因吧
        天口三水羊:@mingmingsky 嗯 一般的app很少用 大部分人看看只用来面试:sweat_smile:
      • 0忧伤0:写得不错 :clap:
        天口三水羊:@0忧伤0 谢谢支持
      • IAMCJ:文章写得不错,但是GCD和线程有很大关系的,不能说没关系吧,会误导人的
        Noah1985:其实这种说法也是一种误导吧。
        实际上,GCD的出现模糊了线程的概念。它强调的是队列和任务,我们使用的时候不需要关心线程是怎么去创建和管理。
        iOS开发中,大部分情况都不需要自行用NSThread来创建,管理线程,用GCD足够了。
        IAMCJ:@天口三水羊 哦哦
        天口三水羊:@耳东米青 嗯 你说的也对 但是我认为一个新手看到thread和gcd搅到一起会迷惘的 所以本文中 基本不会出现线程的概念 目的仅仅是让新手能正确的使用gcd😬😬
      • 9998ffd1889d:大大写的好棒,小白表示受教啦!
        9998ffd1889d:@天口三水羊 如何看出我是个妹子的:scream:
        天口三水羊:@进京的肥羊 谢谢妹子支持
      • 33ee318095dd:好好好
        天口三水羊:@lossloss 谢谢支持
      • 悠辰清泪:mark 一下 顶一个
        天口三水羊:@悠辰清泪 谢谢支持
      • 上帝很忙:好 👌
        天口三水羊:@上帝很忙 谢谢支持

      本文标题:巧谈GCD

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