dispatch_after
功能:延遲一段時間把一項任務提交到隊列中執行,返回之后就不能取消
常用來在在主隊列上延遲執行一項任務
示例代碼:
//創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//立即打印一條信息
NSLog(@"Begin add block...");
//提交一個block
dispatch_async(queue, ^{
//Sleep 10秒
[NSThread sleepForTimeInterval:10];
NSLog(@"First block done...");
});
//5 秒以后提交block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), queue, ^{
NSLog(@"After...");
});
結果如下:
2015-03-31 20:57:27.122 GCDTest[45633:1812016] Begin add block...
2015-03-31 20:57:37.127 GCDTest[45633:1812041] First block done...
2015-03-31 20:57:37.127 GCDTest[45633:1812041] After...
dispatch_after是延遲提交,并不是延時后立即執行
dispatch_once
功能:保證在APP運行期間,block中的代碼只執行一次
不僅意味著代碼僅會被運行一次,而且還是線程安全的,這就意味著你不需要使用諸如@synchronized之類的來防止使用多個線程或者隊列時不同步的問題。
示例代碼:
// 如果你要共享某個實例
+(MyInstance*)shareMyInstance(){
static MyInstance *inst=nil;
static dispatch_once_t p;
dispatch_once(&p,^{
inst=[[MyInstance alloc]init];
});
return inst;
}
現在在應用中就有一個共享的實例,該實例只會被創建一次。
單例方法有很多優勢:
1 線程安全
2 很好滿足靜態分析器要求
3 和自動引用計數(ARC)兼容
4 僅需要少量代碼
dispatch_group
多個并發下載任務,dispatch_group 可以幫我們實現控制在第一時間知道任務全部完成。
dispatch_group_t group = dispatch_group_create();
// 某個任務放進 group
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 任務代碼1
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 任務代碼2
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 任務全部完成處理
NSLog(@"isover");
});
創建一個任務組,然后將異步操作放進組里面,在最后用notify 告知所有任務完成,并做相應處理,一般來說都是在主線程里面刷新UI來提示用戶了。你如果不依賴UI放進子線程里面也是沒有問題的。當然group同步的方式還有其他
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i ++)
{
dispatch_group_enter(group);
// 任務代碼i 假定任務 是異步執行block回調
// block 回調執行
dispatch_group_leave(group);
// block 回調執行
}
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
// 主線程處理
});
首先我們異步執行,因為dispatch_group_wait函數是阻塞的,for里面安排了三個任務,這三個任務都是加載,在任務開始前 調用 enter,任務完成時調用leave,wait函數一直阻塞,直到它發現group里面的任務全部leave,它才放棄阻塞(任務全部完成),然后我們在主線程更新UI告知用戶.
dispatch_group_notify函數會隱式retain 當先的調用者,在使用的時候要知道這一點
dispatch_apply
<li>基本用法:</li>
dispatch_apply函數是dispatch_sync函數和dispatch group的關聯API,該函數按指定的次數將指定的block追加到指定的dispatch queue中,并等到全部的處理執行結束
/*!
* @brief dispatch_apply的用法
*/
- (void)dispatchApplyTest1 {
//生成全局隊列
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);
});
NSLog(@"done");
/*!
* @brief 輸出結果
*
2016-02-25 19:24:39.102 dispatch_apply測試[2985:165004] 0
2016-02-25 19:24:39.102 dispatch_apply測試[2985:165086] 1
2016-02-25 19:24:39.104 dispatch_apply測試[2985:165004] 4
2016-02-25 19:24:39.104 dispatch_apply測試[2985:165004] 5
2016-02-25 19:24:39.104 dispatch_apply測試[2985:165004] 6
2016-02-25 19:24:39.103 dispatch_apply測試[2985:165088] 3
2016-02-25 19:24:39.104 dispatch_apply測試[2985:165004] 7
2016-02-25 19:24:39.105 dispatch_apply測試[2985:165004] 8
2016-02-25 19:24:39.105 dispatch_apply測試[2985:165004] 9
2016-02-25 19:24:39.102 dispatch_apply測試[2985:165087] 2
2016-02-25 19:24:39.105 dispatch_apply測試[2985:165004] done
* !!!因為在Global Dispatch Queue中執行,所以各個處理的執行時間不定
但done一定會輸出在最后的位置,因為dispatch_apply函數會等待所以的處理結束
*/
}
<li>使用技巧:模擬for循環</li>
/*!
* @brief 實例:當要對NSArray類對象的所有元素執行處理時,不必一個一個的編寫for循環部分
*/
- (void)dispatchApplyTest2 {
//1.創建NSArray類對象
NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
//2.創建一個全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.通過dispatch_apply函數對NSArray中的全部元素進行處理,并等待處理完成,
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
NSLog(@"done");
/*!
* @brief 輸出結果
*
2016-02-25 19:37:17.308 dispatch_apply測試[3010:167871] 0: a
2016-02-25 19:37:17.308 dispatch_apply測試[3010:167956] 1: b
2016-02-25 19:37:17.308 dispatch_apply測試[3010:167957] 3: d
2016-02-25 19:37:17.308 dispatch_apply測試[3010:167871] 4: e
2016-02-25 19:37:17.309 dispatch_apply測試[3010:167957] 6: g
2016-02-25 19:37:17.309 dispatch_apply測試[3010:167871] 7: h
2016-02-25 19:37:17.309 dispatch_apply測試[3010:167957] 8: i
2016-02-25 19:37:17.309 dispatch_apply測試[3010:167871] 9: j
2016-02-25 19:37:17.308 dispatch_apply測試[3010:167956] 5: f
2016-02-25 19:37:17.308 dispatch_apply測試[3010:167955] 2: c
* !!!因為在Global Dispatch Queue中執行,所以各個處理的執行時間不定
但done一定會輸出在最后的位置,因為dispatch_apply函數會等待所以的處理結束
*/
}
<li>模擬dispatch_sync的同步效果</li>
在dispatch_async函數中異步執行dispatch_apply函數,模擬dispatch_sync的同步效果
/*!
* @brief 推薦在dispatch_async函數中異步執行dispatch_apply函數
效果 dispatch_apply函數與dispatch_sync函數形同,會等待處理執行結束
*/
- (void)dispatchApplyTest3 {
NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主線程執行用戶界面更新等操作");
});
});
/*!
* @brief 執行結果
*
2016-02-25 19:49:53.189 dispatch_apply測試[3060:171856] 3: d
2016-02-25 19:49:53.189 dispatch_apply測試[3060:171852] 1: b
2016-02-25 19:49:53.189 dispatch_apply測試[3060:171853] 2: c
2016-02-25 19:49:53.189 dispatch_apply測試[3060:171850] 0: a
2016-02-25 19:49:53.189 dispatch_apply測試[3060:171856] 4: e
2016-02-25 19:49:53.189 dispatch_apply測試[3060:171852] 5: f
2016-02-25 19:49:53.190 dispatch_apply測試[3060:171853] 6: g
2016-02-25 19:49:53.190 dispatch_apply測試[3060:171850] 7: h
2016-02-25 19:49:53.190 dispatch_apply測試[3060:171852] 9: j
2016-02-25 19:49:53.190 dispatch_apply測試[3060:171856] 8: i
2016-02-25 19:49:53.218 dispatch_apply測試[3060:171760] 回到主線程執行用戶界面更新等操作
*
*/
}
dispatch_semaphore
當我們在處理一系列線程的時候,當數量達到一定量,在以前我們可能會選擇使用NSOperationQueue來處理并發控制,在GCD中快速的控制并發的方法就是dispatch_semaphore
信號量是一個整形值并且具有一個初始計數值,并且支持兩個操作:信號通知和等待。當一個信號量被信號通知,其計數會被增加。當一個線程在一個信號量上等待時,線程會被阻塞(如果有必要的話),直至計數器大于零,然后線程會減少這個計數。
在GCD中有三個函數是semaphore的操作,分別是:
dispatch_semaphore_create 創建一個semaphore
dispatch_semaphore_signal 發送一個信號
dispatch_semaphore_wait 等待信號
簡單的介紹一下這三個函數,
dispatch_semaphore_create有一個整形的參數,我們可以理解為信號的總量,
dispatch_semaphore_signal是發送一個信號,自然會讓信號總量加1,
dispatch_semaphore_wait等待信號,當信號總量少于0的時候就會一直等待,否則就可以正常的執行,并讓信號總量-1,根據這樣的原理,我們便可以快速的創建一個并發控制來同步任務和有限資源訪問控制。
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
dispatch_release(semaphore);
簡單的介紹一下這一段代碼,創建了一個初使值為10的semaphore,每一次for循環都會創建一個新的線程,線程結束的時候會發送一個信號,線程創建之前會信號等待,所以當同時創建了10個線程之后,for循環就會阻塞,等待有線程結束之后會增加一個信號才繼續執行,如此就形成了對并發的控制,如上就是一個并發數為10的一個線程隊列。
簡單示例:
__block BOOL isok = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
Engine *engine = [[Engine alloc] init];
[engine queryCompletion:^(BOOL isOpen) {
isok = isOpen;
dispatch_semaphore_signal(sema);
} onError:^(int errorCode, NSString *errorMessage) {
isok = NO;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
dispatch_barrier
在并行隊列中,有的時候我們需要讓某個任務單獨執行,也就是他執行的時候不允許其他任務執行。這時候dispatch_barrier就派上了用場。
使用dispatch_barrier將任務加入到并行隊列之后,任務會在前面任務全部執行完成之后執行,任務執行過程中,其他任務無法執行,直到barrier任務執行完成
dispatch_barrier在GCD中有4個API
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
如果API在串行隊列中調用,將等同于dispatch_async、dispatch_async_f、dispatch_sync、dispatch_sync_f,不會有任何影響。
dispatch_barrier最典型的使用場景是讀寫問題,NSMutableDictionary在多個線程中如果同時寫入,或者一個線程寫入一個線程讀取,會發生無法預料的錯誤。但是他可以在多個線程中同時讀取。如果多個線程同時使用同一個NSMutableDictionary。怎樣才能保護NSMutableDictionary不發生意外呢?
- (void)setObject:(id)anObject forKey:(id
)aKey
{
dispatch_barrier_async(self.concurrentQueue, ^{
[self.mutableDictionary setObject:anObject forKey:aKey];
});
}
- (id)objectForKey:(id)aKey
{
__block id object = nil; dispatch_sync(self.concurrentQueue, ^{
object = [self.mutableDictionary objectForKey:aKey];
}); return object;
}
當NSMutableDictionary寫入的時候,我們使用dispatch_barrier_async,讓其單獨執行寫入操作,不允許其他寫入操作或者讀取操作同時執行。當讀取的時候,我們只需要直接使用dispatch_sync,讓其正常讀取即可。這樣就可以保證寫入時不被打擾,讀取時可以多個線程同時進行
dispatch_set(get)_context
先看看這兩個函數的原型:
//設置context
void dispatch_set_context ( dispatch_object_t object, void *context );
//獲取context
void * dispatch_get_context ( dispatch_object_t object );
這里的object一般指的就是通過dispatch_queue_create創建的隊列。
所以,這兩個函數分別完成了將context“綁定”到特定GCD隊列和從GCD隊列獲取對應context的任務。
<li>什么是context</li>
在上述函數原型中,context是一個“void類型指針”,學過C語言的朋友應該都知道,void型指針可以指向任意類型,就是說,context在這里可以是任意類型的指針。
從這里可以得知,我們可以為隊列“set”任意類型的數據,并在合適的時候取出來用。
<li>用malloc創建context并綁定到隊列上</li>
參考Apple官方的例子,我們先用傳統的malloc創建context,看看如下簡短例子:
//定義context,即一個結構體
typedef struct _Data {
int number;
} Data;
//定義隊列的finalizer函數,用于釋放context內存
void cleanStaff(void *context) {
NSLog(@"In clean, context number: %d", ((Data *)context)->number);
//釋放,如果是new出來的對象,就要用delete
free(context);
}
- (void)testBody {
//創建隊列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//創建Data類型context數據并初始化
Data *myData = malloc(sizeof(Data));
myData->number = 10;
//綁定context
dispatch_set_context(queue, myData);
//設置finalizer函數,用于在隊列執行完成后釋放對應context內存
dispatch_set_finalizer_f(queue, cleanStaff);
dispatch_async(queue, ^{
//獲取隊列的context數據
Data *data = dispatch_get_context(queue);
//打印
NSLog(@"1: context number: %d", data->number);
//修改context保存的數據
data->number = 20;
});
}
運行結果:
2015-03-29 20:28:16.854 GCDTest[37787:1443423] 1: context number: 10
2015-03-29 20:28:16.855 GCDTest[37787:1443423] In clean, context number: 20
通過為隊列設置context,我們就能為隊列綁定自定義的數據,然后在合適的時候取出來用。
<li>NSObject類型的context</li>
在Mac、iOS的開發過程中,我們大部分用的都是Foundation框架下的類,就是如NSString、NSDictionary這些NSObject類型的類。
但是上面的dispatch_set(get)_context接受的context參數是C語言類型的,即Core Foundation類型的,我們如何轉換呢?
由于ARC不能管理Core Foundation Object的生命周期,所以我們必須先轉換context的“類型”,以便轉換內存管理權。
<li>__bridge</li>
__bridge: 只做了類型轉換,不修改內存管理權;
__bridge_retained(即CFBridgingRetain)轉換類型,同時將內存管理權從ARC中移除,后面需要使用CFRelease來釋放對象;
__bridge_transfer(即CFBridgingRelease)將Core Foundation的對象轉換為Objective-C的對象,同時將內存管理權交給ARC。
<li>重新定義context</li>
為了方便下面的說明,我們先定義context類。
@interface Data : NSObject
@property(assign, nonatomic) int number;
@end
@implementation Data
//繼承dealloc方法,便于觀察對象何時被釋放
- (void)dealloc {
NSLog(@"Data dealloc...");
}
@end
看,我們繼承了dealloc方法,這樣就能知道Data類型對象什么時候被釋放。
注意:
__bridge的轉換是沒有轉移內存管理權的,這點要特別注意。
如果在傳context對象時,用的是__bridge轉換,那么context對象的內存管理權還在ARC手里,一旦當前作用域執行完,context就會被釋放,而如果隊列的任務用了context對象,就會造成“EXC_BAD_ACCESS”崩潰!
<li>正確的用法</li>
重寫上面的例子,如下:
//定義隊列的finalizer函數,用于釋放context內存
void cleanStaff(void *context) {
//這里用__bridge轉換,不改變內存管理權
Data *data = (__bridge Data *)(context);
NSLog(@"In clean, context number: %d", data.number);
//釋放context的內存!
CFRelease(context);
}
- (void)testBody {
//創建隊列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//創建Data類型context數據并初始化
Data *myData = [Data new];
myData.number = 10;
//綁定context
//這里用__bridge_retained轉換,將context的內存管理權從ARC移除,交由我們自己手動釋放!
dispatch_set_context(queue, (__bridge_retained void *)(myData));
//設置finalizer函數,用于在隊列執行完成后釋放對應context內存
dispatch_set_finalizer_f(queue, cleanStaff);
dispatch_async(queue, ^{
//獲取隊列的context數據
//這里用__bridge轉換,不改變內存管理權
Data *data = (__bridge Data *)(dispatch_get_context(queue));
//打印
NSLog(@"1: context number: %d", data.number);
//修改context保存的數據
data.number = 20;
});
}
解釋:
>在dispatch_set_context的時候用__bridge_retained轉換,將context的內存管理權從ARC移除,交給我們自己管理。
在隊列任務中,用dispatch_get_context獲取context的時候,用__bridge轉換,維持context的內存管理權不變,防止出了作用域context被釋放。
最后用CFRelease釋放context內存。
運行結果:
2015-03-29 21:12:41.631 GCDTest[38131:1465900] 1: context number: 10
2015-03-29 21:12:41.632 GCDTest[38131:1465900] In clean, context number: 20
2015-03-29 21:12:41.632 GCDTest[38131:1465900] Data dealloc...
由結果可知,我們的context對象在最后顯式調用CFRelease才被釋放。
總的來說,就是合理運用__bridge_retained(transfer)關鍵字轉換對象的內存管理權,讓我們自己控制對象的生命周期。
set_specific & get_specific
有時候我們需要將某些東西關聯到隊列上,比如我們想在某個隊列上存一個東西,或者我們想區分2個隊列。GCD提供了dispatch_queue_set_specific方法,通過key,將context關聯到queue上
void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *context, dispatch_function_t destructor);
queue:需要關聯的queue,不允許傳入NULL
key:唯一的關鍵字
context:要關聯的內容,可以為NULL
destructor:釋放context的函數,當新的context被設置時,destructor會被調用
有存就有取,將context關聯到queue上之后,可以通過dispatch_queue_get_specific或者dispatch_get_specific方法將值取出來。
void *dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
void *dispatch_get_specific(const void *key);
dispatch_queue_get_specific: 根據queue和key取出context,queue參數不能傳入全局隊列
dispatch_get_specific: 根據唯一的key取出當前queue的context。如果當前queue沒有key對應的context,則去queue的target queue取,取不著返回NULL,如果對全局隊列取,也會返回NULL
iOS 6之后dispatch_get_current_queue()被廢棄(廢棄的原因這里不多解釋,如果想了解可以看這里),如果我們需要區分不同的queue,可以使用set_specific方法。根據對應的key是否有值來區分
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);