向Grand Central Dispatch(大型中樞調度)派發任務
隊列有三種:
1. main thread
2. serial thread
3. concurrent thread
有兩種方法向dispatch queue(調度隊列)提交任務:
- Block對象
- C 函數
之前講過Block對象,現在來說說C函數。
必需提供給GCD的C函數需要是despatch_function_t類型。它在Apple libraries裏邊是這樣被定義的:
typedef void (*dispatch_function_t)(void*)
使用GCD執行UI相關任務
解決方案:
使用dispatch_get_main_queue方法
討論:
UI相關任務是執行在主線程上的,所以main queue是唯一選擇。我們有兩種方式來分發到主隊列:
- dispatch_async
執行一個block對象到調度隊列 - dispatch_async_f
執行一個C函數到調度隊列
以上兩種方法都是異步方法,dispatch_sync方法不能在主線程上使用,因爲它會阻塞進程,導致App進入死鎖。所有通過GCD提交到主隊列的任務都應該是異步提交的。
dispatch_async
它有兩個參數:
- 調度隊列句柄(dispatch queue handle)
- block對象
來一發無參數無返回值的小例子:
使用Block
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"GCD" message:@"GCD is amazing!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:okAction];
[self showViewController:alertController sender:nil];
});
使用C函數,我們可以提交一個指向application定義的上下文的指針
以下這些內容都寫在AppDelegate.m中
typedef struct{
char *title;
char *message;
char *cancelButtonTitle;
}AlertViewData;
void displayAlertView(void *paramContext){
AlertViewData *alertData = (AlertViewData *)paramContext;
NSString *title = [NSString stringWithUTF8String:alertData->title];
NSString *message = [NSString stringWithUTF8String:alertData->message];
NSString *cancelButtonTitle = [NSString stringWithUTF8String:alertData->cancelButtonTitle];
[[[UIAlertView alloc]initWithTitle:title message:message delegate:nil cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil, nil]show];
free(alertData);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
dispatch_queue_t mainQueue = dispatch_get_main_queue();
AlertViewData *context = (AlertViewData*)malloc(sizeof(AlertViewData));
if (context) {
context->title = "GCD";
context->message = "GCD is amazing!";
context->cancelButtonTitle = "OK";
dispatch_async_f(mainQueue, (void *)context, displayAlertView);
}
return YES;
}
// dispatch_async_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)
參數一:確認隊列;參數二:提交上下文,也就是參數;參數三:調用的C語言方法都是dispatch_function_t類型
我們可以通過NSThread類查看當前隊列和主隊列:
NSLog(@"current thread = %@",[NSThread currentThread]);
NSLog(@"main thread = %@",[NSThread mainThread]);
使用GCD同步執行與UI無關的任務
解決方案:
使用dispatch_sync方法
討論:
任何與UI無關的任務,都可以使用全局併發隊列(globle concurrent queues),它既可以使用同步方法,也允許使用異步方法。
對於同步方法來說,它並不意味着你的應用要等着任務完成後才能繼續,而是說在那個併發線程上的其他任務,需要等待這個隊列上 的當前任務完成後才能繼續執行。你的程序將會一直執行,因爲併發隊列並不執行在主隊列上。(只有一個例外,當一個任務通過dispatch_sync提交到併發隊列concurrent queue或者串行隊列serial queue,如果可能的話,iOS將會讓這個任務執行在當前隊列上,而這個current queue也可能是主線程,它取決於那個時候代碼路徑在哪裏。這個是GCD在編碼時候的一個優化。)
如果你提交一個同步任務到併發隊列,同時提交另一個同步任務到另一個併發隊列,這兩個同步任務對對方來說都是異步執行的,因爲它們是跑在不同的併發隊列上的。
理解以上概念是很重要的。如果你想要確保任務B必須等任務A結束後纔開始,你可以將它們同步的放到同一個隊列上去。
你可以使用dispatch_sync方法執行同步任務在一個調度隊列上,你所需要做的只是,提供一個負責處理任務的隊列,以及一個block代碼來執行在這個隊列上。
下面這個例子,它打印了1-1000兩次,一個隊列完成後接着另一個隊列,並沒有阻塞主線程。我們可以創建一個block對象,來爲我們做計算,並且同步地調用相同的block對象兩次
void (^printFrom1To1000)(void) = ^{
NSUInteger counter = 0;
for(counter = 1 ; counter <= 1000 ; counter++){
NSLog(@"counter = %lu - Thread = %@",(unsigned long)counter,[NSThread currentThread]);
}
};
//-------------------
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(concurrentQueue, printFrom1To1000);
dispatch_sync(concurrentQueue, printFrom1To1000);
運行的結果是,兩個隊列依次執行,把第一個執行完後,再打印第二個1-1000,兩個隊列都執行在主線程上,即使你是使用併發隊列來執行這個任務的。原來,這是GCD的優化。dispatch_sync方法將會使用當前線程(就是你用來調度任務的線程),在任何可能的時候,作爲已經被編入GCD的優化的一部分。蘋果官方是這樣說的:
As an optimization, this function invokes the block on the current thread when possible.
C語言的調用方法是這樣子的:
void printFrom1To1000 (void *paramContext){
NSUInteger counter = 0;
for(counter = 1 ; counter <= 1000 ; counter++){
NSLog(@"counter = %lu - Thread = %@",(unsigned long)counter,[NSThread currentThread]);
}
};
//-------------
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync_f(concurrentQueue, NULL, printFrom1To1000);
dispatch_sync_f(concurrentQueue, NULL, printFrom1To1000);
dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
第一個參數是指定隊列的優先級,可選項分別有:DISPATCH_QUEUE_PRIORITY_HIGH、DISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW
級別越高,任務就會被分配更多的CPU時間片段
第二個參數是保留值,你只需要記住永遠給它賦值0就對了
使用GCD異步執行與UI無關的任務
這是GCD最精華的部分,可以同時在main、serial、concurrent上異步執行block代碼。
解決方案:
1. dispatch_async
2. dispatch_async_f
場景:現在想要從網絡上下載一個圖片,下載完成後顯示給用戶。
思路:
- 在併發隊列上異步發動一個block對象;
- 一旦進入到這個block中,我們就同步發動另一個block對象,使用dispatch_sync從URL上下載一個圖片。我們這樣做是爲了讓這個併發隊列的其他代碼等待圖片下載完成。因此,我們只是令這個併發隊列等待,而不是其他的隊列。總的來說,我們就是在一個異步的隊列中同步的下載一張圖片,我們需要關注的就是下載的時候不要去阻塞主線程;
- 當圖片下載好了之後,我們就會同步的執行一個block對象到主隊列上,是爲了去刷新UI展示圖片。
思路:
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block UIImage *image = nil;
dispatch_sync(concurrentQueue, ^{
/*Down load the image here*/
});
dispatch_sync(dispatch_get_main_queue(), ^{
/*Show the image to the user here on the main queque*/
});
});
再具體一點
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block UIImage *image = nil;
dispatch_sync(concurrentQueue, ^{
NSString *urlAsString = @"http://image2.sina.com.cn/ent"\
"/2004-12-18/1103339247_UQaqtq.jpg";
NSURL *url = [NSURL URLWithString:urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSError *downloadError = nil;
NSData *imageData = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:nil
error:&downloadError];
if (!downloadError && imageData) {
imageData = [UIImage imageWithData:imageData];
}else if(downloadError){
NSLog(@"Error happened = %@",downloadError);
}else{
NSLog(@"No data could get downloaded from the URL");
}
});
dispatch_sync(dispatch_get_main_queue(), ^{
if (image) {
/* Show the image */
}else{
NSLog(@"Image isn't downloaded. Nothing to display");
}
});
});
使用GCD延後執行任務
解決方案:
- dispatch_after
- dispatch_after_f
dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
參數:納秒單位的延遲時間,調度的隊列,Block對象;
dispatch_after_f(<#dispatch_time_t when#>, <#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)
參數:納秒單位的延遲時間,調度的隊列,給C方法使用的上下文,C方法
注意:雖然延遲的單位是納秒,但是它還是由iOS來覺得派遣延遲的粒度大小,因此,延遲的時間也許不會像設定的那麼精確。
代碼示例:
double delayInSeconds = 2.0;
dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(delayInNanoSeconds, concurrentQueue, ^{
/* Perform your operations here */
});
dispatch_time_t類型是一個絕對時間的抽象呈現。爲了得到這個值,你可以使用上面代碼片段中的dispatch_time方法。以下參數是你可以傳遞給dispatch_time方法的:
(Base time,Delta to add to base time)
base time可以設定爲DISPATCH_TIME_NOW。無論最後B或者D設置爲什麼值,最終dispatch_time方法的時間總和就是B+D。
B表示從某一時刻開始的時間,D就是從B開始要延遲的那個時間。
舉例來說:
從現在開始的3s後
dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, 3.f * NSEC_PER_SEC);
或者從現在開始的半秒後
dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, (1/2.f) * NSEC_PER_SEC);
C語言的實現方式:略,不想寫了
用GCD只執行一次任務
解決方法:
dispatch_once
dispatch_once(<#dispatch_once_t *predicate#>, <#^(void)block#>)
參數:
Token:dispatch_once_t類型的token可以保留第一次從GCD中產生的token;
block 對象;
代碼:
static dispatch_once_t onceToken;
void (^executedOnlyOnce)(void) = ^{
static NSUInteger numberOfEntries = 0;
numberOfEntries ++;
NSLog(@"Excuted %lu time(s)",(unsigned long)numberOfEntries);
};
//--------------------------------
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_once(&onceToken, ^{
dispatch_async(concurrentQueue, executedOnlyOnce);
});
dispatch_once(&onceToken, ^{
dispatch_async(concurrentQueue, executedOnlyOnce);
});
執行的結果是:
Excuted 1 time(s)
實例:使用dispatch_once製作singleton。
-(id)sharedInstance{
static MySingleton *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [MySingleton new];
});
return sharedInstance;
}