Grand Central Dispatch是一种异步执行任务技术。Dispatch Queue是执行处理的等待队列。它按照追加的顺序(FIFO)执行处理。Dispatch Queue按照是否等待处理可以分为serial Dispatch Queue和Concurrent Dispatch Queue。serial Dispatch Queue中的block只在一个线程中顺序执行,而Concurrent Dispatch Queue中的block会在多个线程中并行执行。
GCD使用简介
dispatch_queue
dispatch_queue_t
可以通过dispatch_queue_create
生成自定义队列,也可以使用dispatch_get_global_queue
和dispatch_get_main_queue
函数来生成全局队列和主队列。dispatch_queue_create中
的第一个参数label
表示queue的名称,attr
表示是否是并行队列。dispatch_queue_create
创建的队列默认优先级为Default Priority
。
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr);
如果想修改dispatch_queue_create
创建的队列的优先级,可以使用dispatch_set_target_queue
。
dispatch_queue_t serialQueue = dispatch_queue_create("com.cmcc.gcdserial", NULL);
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//设置serialQueue优先级为backgroundQueue的优先级
dispatch_set_target_queue(serialQueue, backgroundQueue);
同时在多个serial dispatch Queue
中,可以通过设置dispatch_set_target_queue
使多个queue串行执行。
dispatch_get_global_queue
用来创建一个全局队列。第一个参数identifier表示队列的优先级,第二个参数为保留字段。优先级有High Priority
、Default Priority
、Low Priority
、Background Priority
4种(在iOS8.0之前)和QOS_CLASS_USER_INTERACTIVE
、QOS_CLASS_USER_INITIATED
、QOS_CLASS_DEFAULT
、QOS_CLASS_UTILITY
、QOS_CLASS_UTILITY
、QOS_CLASS_UNSPECIFIED
5种(iOS8.0及之后)。
全局队列默认都是并发队列。
dispatch_get_global_queue(long identifier, unsigned long flags);
dispatch_get_main_queue
用来获得主队列。主队列是一个串行队列。
dispatch_get_main_queue()
dispatch_async & dispatch_sync
dispatch_async
表示异步执行队列,dispatch_sync
表示同步执行队列。异步就是不等待处理结果,同步就是等待处理结果。不过对于串行队列,dispatch_async
和dispatch_sync
效果一样。
__block NSString *serialStr = @"I am serial";
dispatch_queue_t serialQueue = dispatch_queue_create("com.cmcc.gcdserial", NULL);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_sync(serialQueue, ^{
NSLog(@"no.1 %@",serialStr);
serialStr = @"I am changed";
});
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_sync(serialQueue, ^{
NSLog(@"no.2 %@",serialStr);
});
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_sync(serialQueue, ^{
NSLog(@"no.3 %@",serialStr);
});
});
dispatch_sync
处理不当容易引起死锁。因此使用时需慎重。比如在主线程中执行以下代码。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"I am locked");
});
dispatch_after
dispatch_after
用于延迟一定时间把执行得block加入到队列中。同时这个延时不是精确的,它取决于队列线程runloop,可能会跳过一个runloop周期。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3ull * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"wait at least 3 seconds"); //3s后追加这block到queue
});
dispatch_group
dispatch_group
用于对多个处理全部结束后执行处理结果。group通过dispatch_group_create
创建,queue通过dispatch_get_global_queue
创建。可以通过dispatch_group_async
向group中加入block,或者通过dispatch_group_enter
,dispatch_group_leave
之间加入block。 dispatch_notify
用于前面group中加入的block执行完后通知执行处理结果。dispatch_wait
用于等待前面group中加入的block执行。dispatch_wait
会阻塞线程,dispatch_notify
不会阻塞线程。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
__block NSMutableArray *arr = [NSMutableArray arrayWithCapacity:0];
dispatch_group_async(group, queue, ^{
[arr addObject:@1];
});
dispatch_group_async(group, queue, ^{
[arr addObject:@2];
});
dispatch_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"update UI with arr %@", arr);
});
});
dispatch_barrier
对于数据库和文件的读写,往往希望并行读,同步写。这种情形可以通过dispatch_barrier
实现。barrier就像一个栅栏,把queue分为barrier之前和barrier之后。先执行barrier之前的,再执行barrier之中的,最后执行barrier之后的。
__block NSString *barrierStr = @"barrier init";
dispatch_queue_t barrierqueue = dispatch_queue_create("com.cmcc.gcdbarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(barrierqueue, ^{
NSLog(@"read 1 %@",barrierStr);
});
dispatch_async(barrierqueue, ^{
NSLog(@"read 2 %@",barrierStr);
});
dispatch_barrier_async(barrierqueue, ^{
barrierStr = @"barrier changed";
NSLog(@"write 3 %@",barrierStr);
});
dispatch_async(barrierqueue, ^{
NSLog(@"read 4 %@",barrierStr);
});
dispatch_async(barrierqueue, ^{
NSLog(@"read 5 %@",barrierStr);
});
dispatch_async(barrierqueue, ^{
NSLog(@"read 6 %@",barrierStr);
});
dispatch_apply
dispatch_apply
效果与dispatch_group
效果类似。但是多了一个次数的参数。
dispatch_apply(6, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"done");
执行结果为:
2018-01-03 12:54:57.690983+0800 GCDDemo[19151:3684607] 2
2018-01-03 12:54:57.690987+0800 GCDDemo[19151:3684605] 1
2018-01-03 12:54:57.690990+0800 GCDDemo[19151:3684608] 3
2018-01-03 12:54:57.690993+0800 GCDDemo[19151:3684670] 5
2018-01-03 12:54:57.690988+0800 GCDDemo[19151:3684509] 4
2018-01-03 12:54:57.690983+0800 GCDDemo[19151:3684606] 0
2018-01-03 12:54:57.692172+0800 GCDDemo[19151:3684509] done
dispatch_block
dispatch_block_t是在iOS8及之后引入的,用于监听和取消执行的block。监听通过dispatch_wait
和dispatch_notify
block的执行。另外通过dispatch_block_cancel
来取消执行的block。这个只能取消还没执行的block。
dispatch_queue_t serialQueue = dispatch_queue_create("com.cmcc.serial", NULL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
NSLog(@"start block1");
[NSThread sleepForTimeInterval:3];
NSLog(@"end block1");
});
dispatch_async(serialQueue, block1);
long result = dispatch_wait(block1, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
if (result == 0) {
NSLog(@"success perform block1");
} else {
NSLog(@"time out");
}
dispatch_block_t block2 = dispatch_block_create(0, ^{
NSLog(@"start block2");
[NSThread sleepForTimeInterval:3];
NSLog(@"end block2");
});
dispatch_async(serialQueue, block2);
dispatch_block_cancel(block1);
dispatch_block_cancel(block2);
dispatch_semaphore_t
dispatch_semaphore_t
表示一个信号量。如果信号量不大于0,dispatch_wait
则会阻塞线程。如果用dispatch_semaphore_signal
则会为该semaphore增加一个计数。可以用semaphore来做同步操作。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [NSMutableArray new];
for (int i=0; i<10000; i++) {
dispatch_async(globalQueue, ^{
dispatch_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInt:i]];
NSLog(@"current number is %d",i);
dispatch_semaphore_signal(semaphore);
});
}
dispatch_once
dispatch_once是用来创建单例的,它比传统的单例创建比优势是多线程安全的。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//init
});
dispatch_source
Dispatch Source是BSD内核功能kqueue
的包装。kqueue
是在XNU内核发生各种事件时,在应用程序编程执行处理的方式。
Dispatch Source可以处理以下事件:
名称 | 内容 |
---|---|
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 | 检测到与进程相关的事件 |
DISPATCH_SOURCE_TYPE_READ | 可读取文件映像 |
DISPATCH_SOURCE_TYPE_SIGNAL | 接收信号 |
DISPATCH_SOURCE_TYPE_TIMER | 定时器 |
DISPATCH_SOURCE_TYPE_VNODE | 文件系统有变更 |
DISPATCH_SOURCE_TYPE_WRITE | 可写入文件映像 |
下面是GCDAsyncSocket使用DISPATCH_SOURCE_TYPE_READ
异步读取文件映像的例子。
//1.创建基于DISPATCH_SOURCE_TYPE_READ的dispatch source
accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
int socketFD = socket4FD;
dispatch_source_t acceptSource = accept4Source;
__weak IDMPGCDAsyncSocket *weakSelf = self;
//2.指定read事件发生时执行的处理
dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong IDMPGCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
LogVerbose(@"event4Block");
unsigned long i = 0;
unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
#pragma clang diagnostic pop
}});
//3.指定取消source时执行的处理
dispatch_source_set_cancel_handler(accept4Source, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(accept4Source)");
dispatch_release(acceptSource);
#endif
LogVerbose(@"close(socket4FD)");
close(socketFD);
#pragma clang diagnostic pop
});
LogVerbose(@"dispatch_resume(accept4Source)");
//4.启动dispatch source
dispatch_resume(accept4Source);
下面这个是GCDAsyncSocket使用DISPATCH_SOURCE_TYPE_TIMER
的例子。
//1.创建基于DISPATCH_SOURCE_TYPE_TIMER的dispatch source
connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
__weak IDMPGCDAsyncSocket *weakSelf = self;
//2.设置超时时的处理
dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong IDMPGCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
[strongSelf doConnectTimeout];
#pragma clang diagnostic pop
}});
#if !OS_OBJECT_USE_OBJC
dispatch_source_t theConnectTimer = connectTimer;
//3.设置source取消时的处理
dispatch_source_set_cancel_handler(connectTimer, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
LogVerbose(@"dispatch_release(connectTimer)");
dispatch_release(theConnectTimer);
#pragma clang diagnostic pop
});
#endif
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
//4.设置定时器超时时间
dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0);
//5.启动dispatch source
dispatch_resume(connectTimer);
参考
Dispatch
Objective-C高级编程
细说GCD(Grand Central Dispatch)如何用
CocoaAsyncSocket
深入理解GCD