-
什么是 GCD
首先 GCD (Grand Central Dispatch)是apple 開發的一個多核編程的解決方法.GCD 和 block 的配合使用,可以方便的進行多線程編程.
- 應用舉例
例如在 iPhone 上做一個下載網頁的功能,該功能非常簡單,就是 viewcontroller 上防止一個按鈕,點擊該按鈕時,顯示一個轉動的圓圈,表示正在進行下載,當下載完成后,將內容加載到界面上的一個文本控件中. -
不使用 GCD 前
功能雖簡單,但是我們必須將下載的過程放到后臺的線程中,否則的話會阻塞 UI線程
.所以如果不用 GCD, 我們需要些3個方法
- someClick 方法是點擊按鈕后的代碼,可以看到我們用 NSInvocationOperation 建了一個后臺線程,并且放到 NSOperationQueue 中。后臺線程執行 download 方法
- download 方法處理下載網頁的邏輯。下載完成后用 performSelectorOnMainThread 執行 download_completed 方法。
- download_completed 進行 clear up 的工作,并把下載的內容顯示到文本控件中。
static NSOperationQueue * queue;
//點擊按鈕
- (IBAction)someClick:(id)sender {
self.indicator.hidden = NO;
[self.indicator startAnimating];
queue = [[NSOperationQueue alloc] init];
NSInvocationOperation * op = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil] autorelease];
[queue addOperation:op];
}
//下載任務
- (void)download {
NSURL * url = [NSURL URLWithString:@"http://www.youdao.com"]; NSError * error;
NSString * data = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (data != nil) {
[self performSelectorOnMainThread:@selector(download_completed:) withObject:data waitUntilDone:NO];
} else {
NSLog(@"error when download:%@", error);
[queue release];
}
}
//顯示文本
- (void) download_completed:(NSString *) data {
NSLog(@"call back");
[self.indicator stopAnimating];
self.indicator.hidden = YES;
self.content.text = data; [queue release];
}
-
使用 GCD
如果使用 GCD, 以上的三個方法就可以放到一起
//原始代碼片段一
self.indicator.hidden = NO;
[self.indicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 原始代碼片段二
NSURL * url = [NSURL URLWithString:@"http://www.youdao.com"];
NSErroe *error;
NSString *data = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (data != nil) {
//原始代碼片段三
dispatch_async(dispatch_get_main_queue(), ^{ //回主線程
[self.indicator stopAnimating];
self.indicator.hidden = YES;
self.content.text = data;
});
} else{
NSLog(@"error when download:%@", error);
}
});
代碼塊變短,這是因為少了原來3個方法的定義,也少了相互之間需要傳遞的變量的封裝.
此外呢,代碼變清楚了.雖然是異步的代碼.但是他們被 GCD 合理的整合在一起.邏輯非常清晰.如果應用上 MVC 模式.我們也可以將 ViewController 層的回調函數用 GCD 的方式傳遞給 Model 層,這相比之前用@ selector 的方式,代碼的邏輯關系會更加清楚.
block 的定義
block 的定義有點像函數指針,差別是 block 用^替代了函數指針的*號.
//申明變量
- (void)(^loggerBlock)(void){
//定義
loggerBlock = ^{
NSLog("hello world");
};
//調用
loggerBlock();
}
但是在大多數時候,我們通常使用內聯的方式來定義 Block, 即將它的程序塊寫在調用的函數里面,例如這樣:
dispatch_async(dispatch_get_global_queue(0,0),^{
//something;
});
所以可以看出, block 有如下特點:
1.程序塊可以在代碼總以內聯的方式來定義
2.程序塊可以訪問在創建它的范圍內的可用的變量.
系統提供的 dispatch 的方法
在我們使用的時候,為了更加方便的使用 GCD, 蘋果提供了一些方法方便我們將 block 放在主線程或后臺線程執行,貨值延后執行.
//后臺執行
dispatch_async(dispatch_get_global_queue(0,0),^{
//something
});
//主線程執行
dispatch_async(dispatch_get_main_queue(),^{
//something;
});
//一次執行;
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
//code to be executed once
});
//延遲2秒執行;
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW ,delayInSeconds *NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queue after delay
});
另外dispatch_queue_t
也可以自己定義,如果要自定義 queue
, 可以用dispatch_queue_create
方法來進行定義.
dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);
dispatch_async(urls_queue, ^{
//your code
});
dispatch_release(urls_queue);
另外, GCD 還有一些高級用法,例如讓后臺2個線程并行執行,然后等2個線程都結束后,在匯總執行結果.這個可以用dispatch_group\dispatch_group_async 和 dispatch_group_notify
來實現.
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
// 并行執行的線程一
});
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
// 并行執行的線程二
});
dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
// 匯總結果
});
- 加入要修改 block 之外的變量
一般在默認情況下,在程序塊訪問的外部變量是復制過去的,即寫操作不對原變量生效,但是你可以加上__block來使得其寫操作生效.
__block int a = 0;
void(^foo)(void) = ^{
a =1;
}
foo();
//在這里,a 的值被修改為1;
-此外使用 block 的另一個用處是可以讓程序在后臺較長就的運行.在以前,當 app被按 home 鍵推出后, app僅有最多5秒鐘的時候做一些保存或者清理資源的工作.但是應用可以調用 UIApplication 的beginBackgroundTaskWithExpirationHandler
方法,讓APP 最多有10分鐘的時間在后臺長久運行,這個時間可以用來做清理本地緩存,發送統計數據等工作.
讓程序在后臺長久運行的實例代碼如下;
// AppDelegate.h 文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;
// AppDelegate.m 文件
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self beingBackgroundUpdateTask];
// 在這里加上你需要長久運行的代碼 [self endBackgroundUpdateTask];
}
- (void)beingBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void)endBackgroundUpdateTask{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
例如代碼;
#pragma mark-----|GCD的串行隊列,有序的執行一組任務.
- (void)serialQueue{
//創建一個串行隊列,執行任務的時候,在一個子線程里面.
/*第一個參數:當前隊列的標簽(也就是名字).第二個參數是隊列類型*/
dispatch_queue_t serialQueue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);
//創建任務
/*第一個參數:當前任務所在的隊列.第二個參數是任務的 block 回調,,邏輯代碼就在 block 中*/
dispatch_async(serialQueue, ^{
NSLog(@"當前的線程%@",[NSThread currentThread]);
NSLog(@"小蝌蚪");
});
dispatch_async(serialQueue, ^{
NSLog(@"找媽媽");
NSLog(@"當前的線程%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"十個月的變態發育");
NSLog(@"當前的線程%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"出生");
NSLog(@"當前的線程%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"掛了");
NSLog(@"當前的線程%@",[NSThread currentThread]);
});
}
#pragma mark---并行隊列
- (void)concurrentQueue{
dispatch_queue_t concurrentQueue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
/*通過回調函數的方式來實現任務*/
dispatch_async_f(concurrentQueue, @"天堂", function);
dispatch_async(concurrentQueue, ^{
NSLog(@"當前的線程%@",[NSThread currentThread]);
NSLog(@"睡覺");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"當前的線程%@",[NSThread currentThread]);
NSLog(@"抽煙");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"當前的線程%@",[NSThread currentThread]);
NSLog(@"泰國");
});
}
#pragma mark---回調函數
//聲明一個函數
void function(void *contenct){
NSLog(@"%@",contenct);
}
#pragma mark-----/*在實際使用中,我們常用的 GCD 方式*/
-(void)usefulMethod{
//系統提供的全局隊列
//全局隊列優先級
//預留參數:當前并沒有什么用.
dispatch_queue_t oneQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(oneQueue, ^{
//在此處執行耗時的操作
//當操作完成之后,我們需要回到主線程刷新 UI.GCD回主線程方式
dispatch_async(dispatch_get_main_queue(), ^{
//已經回到主線程,在此處刷新 UI.
});
});
//系統提供的
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
});
});
}
#pragma mark------GCD延時調用
- (void)afterDelay{
//DISPATCH_TIME_NOW:從什么時候開始計時
//第二個參數:從計時開始.多長時間之后執行.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"那安安");
});
//聲明一個 oncetoken 對象
//使用oncetoken
//
// static dispatch_once_t onceToken;
// dispatch_once(&onceToken, ^{
// <#code to be executed once#>
// });
}