iOS开发之多线程笔记(四)之GCD

Grand Central Dispatch

纯C语言,提供了非常多强大的函数

线程相关知识以及GCD概念介绍不是本篇重点这里不做过多涉及

(一)OC篇

只想看swift的可以直接跳过,看下面的swift篇

队列的创建
  • dispatch_queue_create(param1,param2)
     /**
     创建一个队列

     @param "identifier" 队列的唯一标识
     @param DISPATCH_QUEUE_SERIAL 串行队列还是并行队列
     @return 返回实例对象(姑且认为它是个对象)
     */
    dispatch_queue_t queue = dispatch_queue_create("identifier", DISPATCH_QUEUE_SERIAL);
  • param1需要传入一个c语言类型字符串,作用是用来标记当前queue,可以传空.可以通过dispatch_queue_get_label来获取queue的标记.
const *char s = dispatch_queue_get_label(queue);
主队列 dispatch_get_main_queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
全局队列 dispatch_get_global_queue(param1, param2)

会获取一个全局队列,我们姑且理解为系统为我们开启的一些全局线程。我们用priority指定队列的优先级,而flag作为保留字段备用(一般为0)。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
  • param1 传入队列优先级.
  • param2 保留字段,目前没什么用,直接传0.
队列中任务的执行:
  1. 串行(Serial):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
  2. 并发(Concurrent):可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效。
  3. 同步(Synchronous):在当前线程中执行任务,不具备开启新线程的能力
  4. 异步(Asynchronous):在新的线程中执行任务,具备开启新线程的能力
  • 串行队列,同步执行
- (void)sync_serial {
    NSLog(@"mian_start");
    dispatch_queue_t queue = dispatch_queue_create("com.test.id", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        [self taskBegin];
    });
    dispatch_sync(queue, ^{
         [self taskBegin];
    });
    dispatch_sync(queue, ^{
         [self taskBegin];
    });
    NSLog(@"mian_end");
}

- (void)taskBegin {
    NSThread *thread = [NSThread currentThread];
    NSLog(@"tread_begin ,%@",thread);
    [NSThread sleepForTimeInterval:3];
    NSLog(@"tread_end ,%@",thread);
}

控制台日志如下:

2018-01-23 15:53:08.274847+0800 Thread[74746:3368362] mian_start
2018-01-23 15:53:08.275235+0800 Thread[74746:3368362] tread_begin ,<NSThread: 0x604000064e40>{number = 1, name = main}
2018-01-23 15:53:11.276825+0800 Thread[74746:3368362] tread_end ,<NSThread: 0x604000064e40>{number = 1, name = main}
2018-01-23 15:53:11.277165+0800 Thread[74746:3368362] tread_begin ,<NSThread: 0x604000064e40>{number = 1, name = main}
2018-01-23 15:53:14.277633+0800 Thread[74746:3368362] tread_end ,<NSThread: 0x604000064e40>{number = 1, name = main}
2018-01-23 15:53:14.277845+0800 Thread[74746:3368362] tread_begin ,<NSThread: 0x604000064e40>{number = 1, name = main}
2018-01-23 15:53:17.278809+0800 Thread[74746:3368362] tread_end ,<NSThread: 0x604000064e40>{number = 1, name = main}
2018-01-23 15:53:17.279073+0800 Thread[74746:3368362] mian_end

总结:不重新开辟线程,线程中的任务按顺序执行.

  • 并行队列,同步执行
- (void)sync_Concurrent {
    NSLog(@"mian_start");
    dispatch_queue_t queue = dispatch_queue_create("222", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        [self taskBegin];
    });
    dispatch_sync(queue, ^{
        [self taskBegin];
    });
    dispatch_sync(queue, ^{
        [self taskBegin];
    });
    NSLog(@"mian_end");
}

- (void)taskBegin {
    NSThread *thread = [NSThread currentThread];
    NSLog(@"tread_begin ,%@",thread);
    [NSThread sleepForTimeInterval:3];
    NSLog(@"tread_end ,%@",thread);
}

打印日志如下:

2018-01-29 11:53:22.464506+0800 Thread[60281:937006] mian_start
2018-01-29 11:53:22.464832+0800 Thread[60281:937006] tread_begin ,<NSThread: 0x600000073bc0>{number = 1, name = main}
2018-01-29 11:53:25.465294+0800 Thread[60281:937006] tread_end ,<NSThread: 0x600000073bc0>{number = 1, name = main}
2018-01-29 11:53:25.465514+0800 Thread[60281:937006] tread_begin ,<NSThread: 0x600000073bc0>{number = 1, name = main}
2018-01-29 11:53:28.466237+0800 Thread[60281:937006] tread_end ,<NSThread: 0x600000073bc0>{number = 1, name = main}
2018-01-29 11:53:28.466548+0800 Thread[60281:937006] tread_begin ,<NSThread: 0x600000073bc0>{number = 1, name = main}
2018-01-29 11:53:31.467731+0800 Thread[60281:937006] tread_end ,<NSThread: 0x600000073bc0>{number = 1, name = main}
2018-01-29 11:53:31.467904+0800 Thread[60281:937006] mian_end

总结:不开辟新线程,任务按顺序执行.

  • 串行队列,异步执行
- (void)async_serial {
    NSLog(@"mian_start");
    dispatch_queue_t queue = dispatch_queue_create("333", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        [self taskBegin];
    });
    dispatch_async(queue, ^{
        [self taskBegin];
    });
    dispatch_async(queue, ^{
        [self taskBegin];
    });
    NSLog(@"mian_end");
}

- (void)taskBegin {
    NSThread *thread = [NSThread currentThread];
    NSLog(@"tread_begin ,%@",thread);
    [NSThread sleepForTimeInterval:3];
    NSLog(@"tread_end ,%@",thread);
}

日志如下:

2018-01-29 11:56:52.776368+0800 Thread[60281:937006] mian_start
2018-01-29 11:56:52.776701+0800 Thread[60281:937006] mian_end
2018-01-29 11:56:52.776777+0800 Thread[60281:951989] tread_begin ,<NSThread: 0x600000274780>{number = 5, name = (null)}
2018-01-29 11:56:55.779400+0800 Thread[60281:951989] tread_end ,<NSThread: 0x600000274780>{number = 5, name = (null)}
2018-01-29 11:56:55.779647+0800 Thread[60281:951989] tread_begin ,<NSThread: 0x600000274780>{number = 5, name = (null)}
2018-01-29 11:56:58.784670+0800 Thread[60281:951989] tread_end ,<NSThread: 0x600000274780>{number = 5, name = (null)}
2018-01-29 11:56:58.784963+0800 Thread[60281:951989] tread_begin ,<NSThread: 0x600000274780>{number = 5, name = (null)}
2018-01-29 11:57:01.786699+0800 Thread[60281:951989] tread_end ,<NSThread: 0x600000274780>{number = 5, name = (null)}

总结:只会另外开辟一个线程,线程中的任务按顺序执行.而且新开辟的线程不会阻塞当前线程.两个线程中的代码同时执行.

  • 并行队列,异步执行
- (void)async_concurrent {
    NSLog(@"mian_start");
    dispatch_queue_t queue = dispatch_queue_create("333", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [self taskBegin];
    });
    dispatch_async(queue, ^{
        [self taskBegin];
    });
    dispatch_async(queue, ^{
        [self taskBegin];
    });
    NSLog(@"mian_end");
}

- (void)taskBegin {
    NSThread *thread = [NSThread currentThread];
    NSLog(@"tread_begin ,%@",thread);
    [NSThread sleepForTimeInterval:3];
    NSLog(@"tread_end ,%@",thread);
}

日志如下:

2018-01-29 13:07:10.507523+0800 Thread[60281:937006] mian_start
2018-01-29 13:07:10.507810+0800 Thread[60281:937006] mian_end
2018-01-29 13:07:10.507901+0800 Thread[60281:1109308] tread_begin ,<NSThread: 0x6040004654c0>{number = 7, name = (null)}
2018-01-29 13:07:10.507903+0800 Thread[60281:951997] tread_begin ,<NSThread: 0x600000476d80>{number = 8, name = (null)}
2018-01-29 13:07:10.507926+0800 Thread[60281:1109326] tread_begin ,<NSThread: 0x60400046c740>{number = 9, name = (null)}
2018-01-29 13:07:13.512128+0800 Thread[60281:1109308] tread_end ,<NSThread: 0x6040004654c0>{number = 7, name = (null)}
2018-01-29 13:07:13.512173+0800 Thread[60281:951997] tread_end ,<NSThread: 0x600000476d80>{number = 8, name = (null)}
2018-01-29 13:07:13.512221+0800 Thread[60281:1109326] tread_end ,<NSThread: 0x60400046c740>{number = 9, name = (null)}

总结:会开启多个线程,.新开辟的线程不会相互阻塞.多个线程中的任务同时执行.

调度组 dispatch_group_t

项目中,我们有时候可能会遇到这样的场景.需要开启多个线程并发执行任务,最后需要在所有任务执行完成后再做操作.这里我们就可以使用GCD中的调度组来实现:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    NSLog(@"调度组开始执行!!!");
    dispatch_group_async(group, queue, ^{
        for(int i = 0; i < 5; i++) {
            NSLog(@"thread1 -> %d", i);
        }
    });
    dispatch_group_async(group, queue, ^{
        for(int i = 0; i < 5; i++) {
            NSLog(@"thread2 -> %d", i);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for(int i = 0; i < 5; i++) {
            NSLog(@"thread3 -> %d", i);
        }
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"所有循环执行完毕!!!");
    });

打印日志如下:

2018-02-01 17:33:33.687543+0800 Thread[63088:367476] 调度组开始执行!!!
2018-02-01 17:33:33.687879+0800 Thread[63088:367562] thread1 -> 0
2018-02-01 17:33:33.687882+0800 Thread[63088:367553] thread2 -> 0
2018-02-01 17:33:33.687888+0800 Thread[63088:367558] thread3 -> 0
2018-02-01 17:33:33.688551+0800 Thread[63088:367562] thread1 -> 1
2018-02-01 17:33:33.688569+0800 Thread[63088:367553] thread2 -> 1
2018-02-01 17:33:33.688671+0800 Thread[63088:367558] thread3 -> 1
2018-02-01 17:33:33.688963+0800 Thread[63088:367553] thread2 -> 2
2018-02-01 17:33:33.688982+0800 Thread[63088:367562] thread1 -> 2
2018-02-01 17:33:33.689465+0800 Thread[63088:367558] thread3 -> 2
2018-02-01 17:33:33.689706+0800 Thread[63088:367553] thread2 -> 3
2018-02-01 17:33:33.690041+0800 Thread[63088:367562] thread1 -> 3
2018-02-01 17:33:33.690279+0800 Thread[63088:367558] thread3 -> 3
2018-02-01 17:33:33.691043+0800 Thread[63088:367553] thread2 -> 4
2018-02-01 17:33:33.691211+0800 Thread[63088:367562] thread1 -> 4
2018-02-01 17:33:33.691389+0800 Thread[63088:367558] thread3 -> 4
2018-02-01 17:33:33.691583+0800 Thread[63088:367553] thread2 -> 5
2018-02-01 17:33:33.691826+0800 Thread[63088:367562] thread1 -> 5
2018-02-01 17:33:33.692019+0800 Thread[63088:367558] thread3 -> 5
2018-02-01 17:33:33.692796+0800 Thread[63088:367558] 所有循环执行完毕!!!

总结:所有任务执行完成后的操作在 dispatch_group_notify 中写代码即可.

  • 但是这里有一个问题:队列中的任务代码是一段for循环,for循环是同步执行的.如果代码块中的任务是异步执行的,还会是现在的效果吗?
  • 比如: 我要发起3个异步的网络请求,需要等到3个请求都有响应后再做下一步操作.此时我们仍然使用上面的代码是否能实现呢?
  • 这里我用延时操作来模拟异步网络请求延时效果.
- (void)group_asynBlock {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    NSLog(@"group 开始执行 !!!");
    dispatch_group_async(group, queue, ^{
        NSLog(@"block1 begin !!!");
        [self request:1];
        NSLog(@"block1 end !!!");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block2 begin !!!");
        [self request:2];
        NSLog(@"block2 end !!!");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"block3 begin !!!");
        [self request:3];
        NSLog(@"block3 end !!!");
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"group 结束 !!!");
    });
}

- (void)request:(NSInteger)count {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"请求%ld 开始 !!!", count);
        [NSThread sleepForTimeInterval:3];
         NSLog(@"请求%ld 结束 !!!", count);
    });
}

下面我可们可以来猜测一下日志会是怎样的.
我们所期望的是这样的

group 开始执行 !!!
block1 begin !!!
block2 begin !!!
block3 begin !!!
block1 end !!!
block2 end !!!
block3 end !!!
请求2 开始 !!!
请求1 开始 !!!
请求3 开始 !!!
请求2 结束 !!!
请求1 结束 !!!
请求3 结束 !!!
group 结束 !!!

但是实际日志是这样的:

2018-02-01 17:55:42.008339+0800 Thread[67540:402087] group 开始执行 !!!
2018-02-01 17:55:42.008609+0800 Thread[67540:402136] block1 begin !!!
2018-02-01 17:55:42.008619+0800 Thread[67540:402132] block2 begin !!!
2018-02-01 17:55:42.008631+0800 Thread[67540:402133] block3 begin !!!
2018-02-01 17:55:42.009244+0800 Thread[67540:402136] block1 end !!!
2018-02-01 17:55:42.009277+0800 Thread[67540:402132] block2 end !!!
2018-02-01 17:55:42.009280+0800 Thread[67540:402134] 请求1 开始 !!!
2018-02-01 17:55:42.009309+0800 Thread[67540:402135] 请求2 开始 !!!
2018-02-01 17:55:42.009444+0800 Thread[67540:402133] block3 end !!!
2018-02-01 17:55:42.009456+0800 Thread[67540:402136] 请求3 开始 !!!
2018-02-01 17:55:42.010551+0800 Thread[67540:402133] group 结束 !!!
2018-02-01 17:55:45.013680+0800 Thread[67540:402135] 请求2 结束 !!!
2018-02-01 17:55:45.013673+0800 Thread[67540:402134] 请求1 结束 !!!
2018-02-01 17:55:45.014906+0800 Thread[67540:402136] 请求3 结束 !!!

可以看到,group执行结束代码的时机是不确定的,这里请求还没结束,group notify你面的代码却已经执行了.

下面来说下group notify 什么时候会调用:

  • 当所有的group中的代码块中的代码从上至下执行完成后就会调用notify.所以如果代码块中都是同步执行的还好,如果出现了异步执行的情况,因为即使是异步,当前代码块中的代码仍然会往下继续执行.所以无论异步执行的任务是否完成并不能起到决定作用.于是就出现了上面的问题.

  • 那我们如何来解决group中存在异步执行代码的情况呢.
    此时需用到 dispatch_group_enter() 和 dispatch_group_leave()
    上面代码需要做如下修改

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    NSLog(@"group 开始执行 !!!");
   
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"block1 begin !!!");
        [self request:1 block:^{
            dispatch_group_leave(group);
        }];
        NSLog(@"block1 end !!!");
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"block2 begin !!!");
        [self request:2 block:^{
            dispatch_group_leave(group);
        }];
        NSLog(@"block2 end !!!");
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"block3 begin !!!");
        [self request:3 block:^{
            dispatch_group_leave(group);
        }];
        NSLog(@"block3 end !!!");
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"group 结束 !!!");
    });

- (void)request:(NSInteger)count block:(void(^)(void))complete{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"请求%ld 开始 !!!", count);
        [NSThread sleepForTimeInterval:3];
         NSLog(@"请求%ld 结束 !!!", count);
        if(complete)
            complete();
    });
}

日志如下:

2018-04-04 10:57:03.655066+0800 Thread[13280:128963] group 开始执行 !!!
2018-04-04 10:57:03.655381+0800 Thread[13280:129076] block1 begin !!!
2018-04-04 10:57:03.655385+0800 Thread[13280:129085] block2 begin !!!
2018-04-04 10:57:03.655445+0800 Thread[13280:129078] block3 begin !!!
2018-04-04 10:57:03.657045+0800 Thread[13280:129076] block1 end !!!
2018-04-04 10:57:03.657073+0800 Thread[13280:129079] 请求1 开始 !!!
2018-04-04 10:57:03.657078+0800 Thread[13280:129085] block2 end !!!
2018-04-04 10:57:03.657113+0800 Thread[13280:129078] block3 end !!!
2018-04-04 10:57:03.657184+0800 Thread[13280:129076] 请求2 开始 !!!
2018-04-04 10:57:03.657296+0800 Thread[13280:129085] 请求3 开始 !!!
2018-04-04 10:57:06.659375+0800 Thread[13280:129085] 请求3 结束 !!!
2018-04-04 10:57:06.659375+0800 Thread[13280:129076] 请求2 结束 !!!
2018-04-04 10:57:06.659403+0800 Thread[13280:129079] 请求1 结束 !!!
2018-04-04 10:57:06.659753+0800 Thread[13280:129076] group 结束 !!!

如上,结果和我们所期望的一致.在每一个异步任务开始之前,我们需要调用dispatch_group_enter(group)来防止block块内代码执行完毕后,退出group.然后在异步请求响应后的回调里调用dispatch_group_leave(group)使其正常离开group就ok了.

dispatch_group_enter()和dispatch_group_leave()需要成对出现

GCD 栅栏方法:dispatch_barrier_async
  • 一般使用场景,我们开启了多个异步任务.但是需要将其分组,并控制每组的执行顺序的时候可以使用该api
- (void)barrier {
    NSInteger count = 3;
    dispatch_queue_t queue = dispatch_queue_create("queue_label", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for(int i = 0; i < count; i++) {
            NSLog(@"Thread1 --> %d", i);
        }
    });
    
    dispatch_async(queue, ^{
        for(int i = 0; i < count; i++) {
            NSLog(@"Thread2 --> %d", i);
        }
    });
    
    dispatch_barrier_async(queue, ^{
        for(int i = 0; i < count; i++) {
            NSLog(@"barrier --> %d", i);
        }
    });
    
    dispatch_async(queue, ^{
        for(int i = 0; i < count; i++) {
            NSLog(@"Thread3 --> %d", i);
        }
    });
}

日志如下:

2018-04-04 14:38:29.095615+0800 Thread[15169:299036] Thread2 --> 0
2018-04-04 14:38:29.095611+0800 Thread[15169:298974] Thread1 --> 0
2018-04-04 14:38:29.095876+0800 Thread[15169:299036] Thread2 --> 1
2018-04-04 14:38:29.095876+0800 Thread[15169:298974] Thread1 --> 1
2018-04-04 14:38:29.096505+0800 Thread[15169:299036] Thread2 --> 2
2018-04-04 14:38:29.096861+0800 Thread[15169:298974] Thread1 --> 2
2018-04-04 14:38:29.097109+0800 Thread[15169:298974] barrier --> 0
2018-04-04 14:38:29.097212+0800 Thread[15169:298974] barrier --> 1
2018-04-04 14:38:29.097304+0800 Thread[15169:298974] barrier --> 2
2018-04-04 14:38:29.097414+0800 Thread[15169:298974] Thread3 --> 0
2018-04-04 14:38:29.097504+0800 Thread[15169:298974] Thread3 --> 1
2018-04-04 14:38:29.097908+0800 Thread[15169:298974] Thread3 --> 2

可以看出barrier之前的任务会先执行,barrier之后的代码会后执行.barrier顾名思义起到了一个分割的作用.

GCD挂起和恢复
  • dispatch_suspend()挂起某一个队列,并不是真正意义上的停止,已经开始执行的代码块是不会停止的.只会停止执行还未执行的block代码块
  • dispatch_resume()恢复队列任务的执行.和suspend成对出现.
- (void)suspend {
    dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
    //提交第一个block,延时5秒打印。
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:5];
        NSLog(@"五秒后打印,队列挂起时已经开始执行,");
    });
    //提交第二个block,也是延时5秒打印
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:5];
        NSLog(@"队列挂起时未执行,需恢复队列后在执行");
    });
    //延时一秒
    NSLog(@"立刻打印~~~~~~~");
    [NSThread sleepForTimeInterval:1];
    //挂起队列
    NSLog(@"一秒后打印,队列挂起");
    dispatch_suspend(queue);
    //延时10秒
    [NSThread sleepForTimeInterval:11];
    NSLog(@"十秒后打印,开启队列");
    //恢复队列
    dispatch_resume(queue);
}

日志如下:

2018-04-04 16:46:12.574933+0800 Thread[17344:474988] 立刻打印~~~~~~~
2018-04-04 16:46:13.576395+0800 Thread[17344:474988] 一秒后打印,队列挂起
2018-04-04 16:46:17.576697+0800 Thread[17344:475108] 五秒后打印,队列挂起时已经开始执行,
2018-04-04 16:46:24.577738+0800 Thread[17344:474988] 十秒后打印,开启队列
2018-04-04 16:46:29.583528+0800 Thread[17344:475104] 队列挂起时未执行,需恢复队列后在执行
GCD 快速迭代方法:dispatch_apply

类似for循环,可以用来遍历

- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zd", index);
    });
    NSLog(@"遍历结束 !!!");
}

日志如下:

2018-04-04 17:11:42.159210+0800 Thread[18256:512357] 1
2018-04-04 17:11:42.159218+0800 Thread[18256:512356] 2
2018-04-04 17:11:42.159241+0800 Thread[18256:512358] 3
2018-04-04 17:11:42.159210+0800 Thread[18256:512257] 0
2018-04-04 17:11:42.159453+0800 Thread[18256:512357] 4
2018-04-04 17:11:42.159453+0800 Thread[18256:512358] 5
2018-04-04 17:11:42.159454+0800 Thread[18256:512356] 6
2018-04-04 17:11:42.159465+0800 Thread[18256:512257] 7
2018-04-04 17:11:42.159568+0800 Thread[18256:512357] 8
2018-04-04 17:11:42.159741+0800 Thread[18256:512358] 9
2018-04-04 17:11:42.160506+0800 Thread[18256:512257] 遍历结束 !!!
  • 这里不是有序遍历是因为,我们上面创建的是一个全局队列,是异步执行的.所以顺序是随机的.如果想要顺序遍历,可以创建一个串行队列.

附:demo地址

未完待续
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容

  • 背景 担心了两周的我终于轮到去医院做胃镜检查了!去的时候我都想好了最坏的可能(胃癌),之前在网上查的症状都很相似。...
    Dely阅读 9,229评论 21 42
  • 1. GCD简介 什么是GCD呢?我们先来看看百度百科的解释简单了解下概念 引自百度百科:Grand Centra...
    千寻_544f阅读 362评论 0 0
  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 1,018评论 0 4
  • 今天在网络上看到了一个视频片段,应该是一部电视剧还是电影里的,没有找到具体的出处。看完以后随便写了点字,视频里的对...
    枯叶草迷图阅读 771评论 1 2
  • 走在金州和平路和三里桥市场的这段路上,我看到的是让人发指的恶心,路边堆积如山的蒜皮,这个地方离金州规划最好的...
    然其阅读 213评论 0 2