1.什麼是GCD
全稱是Grand Central Dispatch,可譯為“牛X的中樞調度器”
純C語言,提供了非常多強大的函數
2.GCD的優勢
GCD是蘋果公司為多核的並行運算提出的解決方案
GCD會自動利用更多的CPU內核(比如雙核、四核)
GCD會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
程序員只需要告訴GCD想要執行什麼任務,不需要編寫任何線程管理代碼
3.GCD中有2個核心概念
任務:執行什麼操作
隊列:用來存放任務
將任務添加到隊列中
GCD會自動將隊列中的任務取出,放到對應的線程中執行
任務的取出遵循隊列的FIFO原則:先進先出,後進後出
有4個術語比較容易混淆:
同步、異步、並發、串行
同步和異步決定了要不要開啟新的線程
同步:在當前線程中執行任務,不具備開啟新線程的能力
異步:在新的線程中執行任務,具備開啟新線程的能力
並發和串行決定了任務的執行方式
並發:多個任務並發(同時)執行
串行:一個任務執行完畢後,再執行下一個任務
代碼分析上面的四個概念:
#pragma mark - 1異步並發
- (void)asyncGlobal {
// 取得全局並發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 使用異步調用
dispatch_async(queue, ^{
NSLog(@"result1, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"result2, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"result3, %@",[NSThread currentThread]);
});
}
#pragma mark - 2異步串行
- (void)asyncSerial {
// 創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("Serial", NULL);
// 使用異步調用
dispatch_async(queue, ^{
NSLog(@"result1, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"result2, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"result3, %@",[NSThread currentThread]);
});
}
#pragma mark - 3同步並發
- (void)syncGlobal {
// 取得全局並發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 使用同步調用
dispatch_sync(queue, ^{
NSLog(@"result1, %@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"result2, %@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"result3, %@",[NSThread currentThread]);
});
}
#pragma mark - 4同步串行
- (void)syncSerial {
// 創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("Serial", NULL);
// 使用同步調用
dispatch_sync(queue, ^{
NSLog(@"result1, %@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"result2, %@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"result3, %@",[NSThread currentThread]);
});
}
上面的代碼,我們可以通過觀察打印線程信息 [NSThread currentThread] 來判斷是否有開啟新的線程及開啟新的線程的情況。上面的執行結果,可以用下面的圖表來描述:
用文字總結,結果如下:
同步 (sync) 不開起新的線程
並發隊列: 不開起新的線程(用默認的主線程)
串行隊列: 不開起新的線程(用默認的主線程)
異步 (async) 開啟新的線程
並發隊列: 能開啟多條線程(取出隊列中的任務,子線程執行;接著取出隊列中後面的任務,開辟其他線程執行這條任務....)
串行隊列: 只開啟一條子線程(取出隊列中的任務,子線程執行,執行完畢後,繼續取出任務,子線程繼續執行....)
4.線程間通信示例
從子線程回到主線程
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//執行耗時的異步操作...
dispatch_async(dispatch_get_main_queue(),^{
// 回到主線程,執行UI刷新操作
});
});
上面的代碼片段是經典的線程間通信使用方法。
Example:
從網絡下載圖片資源,並顯示到主線程UI上面
- // 並發隊列
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- // 開啟子線程
- dispatch_async(queue, ^{
- NSURL *url = [NSURL URLWithString:@"http://pic.cnr.cn/list/201209/W020120914588236390160.jpg"];
- NSData *data = [NSData dataWithContentsOfURL:url];
- UIImage *image = [UIImage imageWithData:data];
- // 講image信息在主線程上面呈現(這樣就不會卡死了)
- dispatch_async(dispatch_get_main_queue(), ^{
- self.myImageView.image = image;
- });
- });
5. 隊列組
有這麼一種需求
首先:分別異步執行兩個耗時的操作
其次:等兩個異步操作都執行完畢後,再回到主線程執行操作
- dispatch_group_t group = dispatch_group_create();
- dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- // 執行1個耗時的異步操作
- });
- dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- // 執行1個耗時的異步操作
- });
- dispatch_group_notify(group, dispatch_get_main_queue(), ^{
- // 等前面的異步操作都執行完畢後,回到主線程...
- });
Example:
從網絡異步下載兩張圖片,當兩張圖片均下載完畢後,完成圖片的拼接操作
效果圖如下:
實現代碼如下:
- #pragma mark - 並發下載多張圖片
- - (void)downloadImage {
- dispatch_group_t group = dispatch_group_create();
- __block UIImage *image1 = nil;
- dispatch_group_async(group, GlobalQueue, ^{
- image1 = [self imageWithUrl:@"http://www.2qqtouxiang.cn/uploads/allimg/100917/1_100917230241_2.jpg"];
- });
- __block UIImage *image2 = nil;
- dispatch_group_async(group, GlobalQueue, ^{
- image2 = [self imageWithUrl:@"http://img3.douban.com/view/photo/albumicon/public/p1446733286.jpg"];
- });
- // 上面兩個GCD是異步同時下載圖片的,下面這句代碼是等到兩張圖片全部下載完畢後便會執行
- dispatch_group_notify(group, MainQueue, ^{
- self.image1.image = image1;
- self.image2.image = image2;
- // 拼接圖片
- UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, 0.0);
- [image1 drawInRect:CGRectMake(0, 0, 50, 100)];
- [image2 drawInRect:CGRectMake(50, 0, 50, 100)];
- self.myImage.image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- });
- }
- - (UIImage *)imageWithUrl:(NSString *)urlStr {
- NSURL *url = [NSURL URLWithString:urlStr];
- NSData *data = [NSData dataWithContentsOfURL:url];
- return [UIImage imageWithData:data];
- }
6. GCD其他好用的方法
- #pragma mark - 延時執行
- - (void)delay {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(33 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- NSLog(@"延時3秒執行~~~");
- });
- }
- #pragma mark - 不管調用多少次只執行一次
- - (void)doOnce {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSLog(@"一次性執行");
- });
- }