Grand Central Dispatch是一種異步執行任務技術。Dispatch Queue是執行處理的等待隊列。它按照追加的順序(FIFO)執行處理。Dispatch Queue按照是否等待處理可以分為serial Dispatch Queue和Concurrent Dispatch Queue。serial Dispatch Queue中的block只在一個線程中順序執行,而Concurrent Dispatch Queue中的block會在多個線程中并行執行。
1.GCD使用簡介
1.1 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()
1.2 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");
});
1.3 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
});
1.4 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);
});
});
1.5 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);
});
1.6 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
1.7 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);
1.8 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);
});
}
1.9 dispatch_once
dispatch_once是用來創建單例的,它比傳統的單例創建比優勢是多線程安全的。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//init
});
1.10 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);
2. 參考
Dispatch
Objective-C高級編程
細說GCD(Grand Central Dispatch)如何用
CocoaAsyncSocket
深入理解GCD