今天溫故了一下有關(guān) GCD 編程的相關(guān)知識(shí),做了個(gè)小Demo,梳理一下。其中也是用到在github上下載的別的大牛封裝好的一個(gè)GCD源代碼,使用很方便,另外里面也用到了SDWebImage這個(gè)第三方庫,設(shè)置網(wǎng)絡(luò)圖片
Demo的源代碼放在 Coding.net 上,有需要可以下載查看。
主要內(nèi)容
- GCD串行隊(duì)列與并發(fā)隊(duì)列
- GCD延時(shí)執(zhí)行
- GCD線程組
- GCD定時(shí)器
- GCD信號(hào)量
- GCD綜合使用示例
GCD串行隊(duì)列與并發(fā)隊(duì)列
- 串行隊(duì)列:串行隊(duì)列一次只執(zhí)行一個(gè)線程,按照添加到隊(duì)列的順序依次執(zhí)行
- 并發(fā)隊(duì)列: 一次可以執(zhí)行多個(gè)線程,線程之間沒有先后順序
- UI界面所在線程是串行隊(duì)列
首先創(chuàng)建串行隊(duì)列
// 創(chuàng)建串行隊(duì)列
- (void)serailQueue {
// 創(chuàng)建隊(duì)列
GCDQueue *queue = [[GCDQueue alloc] initSerial];
//執(zhí)行隊(duì)列中的線程1
[queue execute:^{
NSLog(@"線程1");
}];
//執(zhí)行隊(duì)列中的線程2
[queue execute:^{
NSLog(@"線程2");
}];
//執(zhí)行隊(duì)列中的線程3
[queue execute:^{
NSLog(@"線程3");
}];
//執(zhí)行隊(duì)列中的線程4
[queue execute:^{
NSLog(@"線程4");
}];
//執(zhí)行隊(duì)列中的線程5
[queue execute:^{
NSLog(@"線程5");
}];
}
在 viewDidLoad里調(diào)用該方法
// 執(zhí)行串行隊(duì)列
[self serailQueue];
輸出結(jié)果能看出,線程執(zhí)行是按順序依次執(zhí)行的

再看并發(fā)隊(duì)列
// 創(chuàng)建并發(fā)隊(duì)列
- (void)initConcurrent {
// 創(chuàng)建隊(duì)列
GCDQueue *queue = [[GCDQueue alloc] initConcurrent];
//執(zhí)行隊(duì)列中的線程1
[queue execute:^{
NSLog(@"線程1");
}];
//執(zhí)行隊(duì)列中的線程2
[queue execute:^{
NSLog(@"線程2");
}];
//執(zhí)行隊(duì)列中的線程3
[queue execute:^{
NSLog(@"線程3");
}];
//執(zhí)行隊(duì)列中的線程4
[queue execute:^{
NSLog(@"線程4");
}];
//執(zhí)行隊(duì)列中的線程5
[queue execute:^{
NSLog(@"線程5");
}];
}
在 viewDidLoad里調(diào)用該方法
// 執(zhí)行并發(fā)隊(duì)列
[self initConcurrent];
此時(shí)的輸出結(jié)果就不再遵循有序執(zhí)行了,如下圖:

UI界面所在的線程是串行隊(duì)列
這個(gè)知識(shí)點(diǎn),也通過寫一段代碼來加載一張網(wǎng)絡(luò)圖片來演示
首先,聲明兩個(gè)屬性
@interface ViewController ()
// GCD 執(zhí)行隊(duì)列與UI界面所在線程隊(duì)列
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIImage *image;
@end
然后寫一個(gè) UI界面所在的線程是串行隊(duì)列的方法
- (void)UIQueue {
self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
self.imageView.center = self.view.center;
[self.view addSubview:self.imageView];
[GCDQueue executeInGlobalQueue:^{
// 處理業(yè)務(wù)邏輯
// 網(wǎng)絡(luò)測(cè)試圖片url
NSURL *imgUrl = [NSURL URLWithString:@"http://o7mwf03sy.bkt.clouddn.com/1460334144112.jpg"];
// 處理義務(wù)邏輯
NSLog(@"處理業(yè)務(wù)邏輯");
[GCDQueue executeInMainQueue:^{
// 獲取圖片更新UI
[self.imageView sd_setImageWithURL:imgUrl];
// 更新UI
NSLog(@"更新UI");
}];
}];
}
控制臺(tái)輸出和模擬器效果如下:

從結(jié)果我們也不難看出,UI所在的線程隊(duì)列是串行隊(duì)列。 所以有個(gè)注意的地方就是,在處理業(yè)務(wù)邏輯的地方,如果耗費(fèi)的資源和時(shí)間較多,就會(huì)阻塞主線程的執(zhí)行。
GCD延時(shí)執(zhí)行
在測(cè)試 GCD延時(shí)執(zhí)行 執(zhí)行前面,我們先來看一下 普通的 NSThread 延時(shí)執(zhí)行 效果:
先寫一個(gè)方法:
- (void)setNSThread {
NSLog(@"啟動(dòng)");
[self performSelector:@selector(threadEvent:)
withObject:self
afterDelay:2.f]; // 延時(shí)2秒執(zhí)行
}
// NSThread 延時(shí)執(zhí)行的事件
- (void)threadEvent:(id)sender {
NSLog(@"NSThread 延時(shí)執(zhí)行事件");
}
NSThread延時(shí)執(zhí)行不僅要寫方法,還要寫一個(gè)執(zhí)行事件的方法,調(diào)用方法運(yùn)行結(jié)果如下:

正好延時(shí)了2秒, 再來看 GCD延時(shí)執(zhí)行 方法,代碼相對(duì)來說比較簡(jiǎn)單
- (void)setGCDEvent {
NSLog(@"啟動(dòng)");
[GCDQueue executeInMainQueue:^{
NSLog(@"GCD 延時(shí)執(zhí)行事件");
} afterDelaySecs:2.f];
}
不需要額外的聲明方法,調(diào)用方法后運(yùn)行的結(jié)果是這樣的:

也能達(dá)到我們要的延遲效果
這里之前有個(gè)誤區(qū), 簡(jiǎn)單看打印時(shí)間覺得
NSTimer延時(shí)執(zhí)行任務(wù)是比較精準(zhǔn)的,但最近查了一些資料,其實(shí)NSTimer是不精準(zhǔn)的, 因?yàn)樗皇且粋€(gè)實(shí)時(shí)系統(tǒng), 如果此時(shí)程序是多線程的, 而NSTimer又處在其中一個(gè)runloop中的某一特定 Mode 中,由于多線程都是分時(shí)執(zhí)行的, 所以當(dāng)NSTimer需要執(zhí)行任務(wù)的時(shí)候,很有可能當(dāng)前線程還在執(zhí)行任務(wù),此時(shí)NSTimer則會(huì)等待其執(zhí)行完畢才會(huì)執(zhí)行延時(shí)任務(wù),所以說,NSTimer是不精準(zhǔn)的.
除此之外, GCD延遲執(zhí)行 還可以取消延時(shí)事件的執(zhí)行,只需在執(zhí)行延時(shí)執(zhí)行前加一行代碼
[NSObject cancelPreviousPerformRequestsWithTarget:self];
而NSTimer 沒有這個(gè)方法。
GCD線程組
在開發(fā)中,我們可能會(huì)有這樣的要求:就是在執(zhí)行完畢線程1和線程2之后,才去執(zhí)行線程3。有一種方法是設(shè)置一個(gè)標(biāo)志,線程1執(zhí)行完了標(biāo)志為1,線程2執(zhí)行完了再+1變成2,線程3執(zhí)行的時(shí)候先判斷當(dāng)前標(biāo)志的值是多少,再來決定是否執(zhí)行,貌似這種方法是可行的,但總感覺是不科學(xué)的,我們完全可以用GCD線程組來完成這個(gè)需求;
首先聲明方法
- (void)setGCDGroup {
// 初始化線程組
GCDGroup *group = [[GCDGroup alloc] init];
// 創(chuàng)建一個(gè)線程隊(duì)列
GCDQueue *queue = [[GCDQueue alloc] initConcurrent];
// 讓線程在group中執(zhí)行 線程1
[queue execute:^{
sleep(1);// 休眠
NSLog(@"線程1執(zhí)行完畢");
} inGroup:group];
// 讓線程在group中執(zhí)行 線程2
[queue execute:^{
sleep(3);
NSLog(@"線程2執(zhí)行完畢");
} inGroup:group];
// 監(jiān)聽線程組是否執(zhí)行完畢,然后執(zhí)行線程3
[queue notify:^{
NSLog(@"線程3執(zhí)行完畢");
} inGroup:group];
}
為了展示效果,這里讓線程1休眠1秒執(zhí)行,線程2休眠3秒執(zhí)行(也就是在線程1之后2秒執(zhí)行),看看是否是我們想要的結(jié)果。

可見,GCD 線程組 實(shí)現(xiàn)了我們想要的結(jié)果.線程組的作用一般就是實(shí)現(xiàn)監(jiān)聽響應(yīng)的線程執(zhí)行完畢后才執(zhí)行。
GCDTimer 定時(shí)器
同樣,為了展示效果,我們還是先來看看NSTimer 的定時(shí)執(zhí)行效果
首先,聲明兩個(gè)私有屬性
@property (nonatomic, strong) GCDTimer *gcdTimer;
@property (nonatomic, strong) NSTimer *normalTimer;
接著,創(chuàng)建 NSTimer方法
- (void)runNSTimer {
// 初始化并激活NSTimer
self.normalTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(timerEvent)
userInfo:nil
repeats:YES];
}
// NSTimer 執(zhí)行時(shí)間
- (void)timerEvent {
NSLog(@"運(yùn)行NSTimer");
}
調(diào)用該方法執(zhí)行結(jié)果為

再來看GCDTimer
聲明方法:
- (void)runGCDTimer {
// 初始化GCDTimer
self.gcdTimer = [[GCDTimer alloc] initInQueue:[GCDQueue mainQueue]];
// 指定間隔時(shí)間
[self.gcdTimer event:^{
NSLog(@"運(yùn)行GCDTimer");
} timeInterval:NSEC_PER_SEC];// NSEC_PER_SEC 宏 代表1 秒
// 運(yùn)行GCDTimer
[self.gcdTimer start];
}
調(diào)用該方法執(zhí)行結(jié)果為

可以看到,
GCDTimer雖然在打印時(shí)間上有那么一點(diǎn)點(diǎn)誤差, 但其實(shí) GCD 的延時(shí)操作才是最為精準(zhǔn)的,因?yàn)?code>NSTimer是運(yùn)行在Runloop里面的,而RunLoop是在 GCD 基礎(chǔ)上實(shí)現(xiàn)的,所以說 GCD 可算是更加精準(zhǔn). 并且如果把NSTimer運(yùn)行在一些例如UITableView里面,可能會(huì)出現(xiàn)一些奇怪的問題,而GCDTimer不存在這個(gè)問題。
GCDSemaphore (GCD信號(hào)量)
先來看看我們正常調(diào)用一個(gè)異步線程方法的輸出結(jié)果
- (void)setGCDSemaphore {
// 異步線程 1
[GCDQueue executeInGlobalQueue:^{
NSLog(@"異步線程 1");
}];
// 異步線程 2
[GCDQueue executeInGlobalQueue:^{
NSLog(@"異步線程 2");
}];
}
在異步線程執(zhí)行里,我們是無法確定是哪個(gè)線程先執(zhí)行完畢的,從輸出結(jié)果就能看出,第一次是這樣的:

第二次是這樣的:

第三次是這樣的:

、、、、、
以此可見,它的執(zhí)行順序是不固定的,這個(gè)時(shí)候,如果使用GCDSemaphore,就能按著我們的意愿順序執(zhí)行線程了。
創(chuàng)建GCDSemaphore并在響應(yīng)位置加入消息指令:
- (void)setGCDSemaphore {
// 創(chuàng)建GCDSemaphore 信號(hào)量
GCDSemaphore *semaphore = [[GCDSemaphore alloc] init];
// 異步線程 1
[GCDQueue executeInGlobalQueue:^{
NSLog(@"異步線程 1");
// 發(fā)送執(zhí)行完畢信號(hào)
[semaphore signal];
}];
// 異步線程 2
[GCDQueue executeInGlobalQueue:^{
// 等待接收?qǐng)?zhí)行完畢信號(hào)才開始執(zhí)行進(jìn)程
[semaphore wait];
NSLog(@"異步線程 2");
}];
// 作用: 必須線程1先執(zhí)行完畢,然后在執(zhí)行線程2
// 將異步線程轉(zhuǎn)化成同步線程。
}
這樣,每次輸出順序就是一樣的

這樣,當(dāng)我們有這種按順序執(zhí)行的特殊需求時(shí),
GCDSemaphore就可以發(fā)揮很好的作用了。
GCD 綜合使用介紹示例
這里我們通過并發(fā)下載三張網(wǎng)絡(luò)圖片的例子來延時(shí) GCD 的強(qiáng)大作用。首先生命三個(gè)私有屬性
@property (nonatomic, strong) UIImageView *view1;
@property (nonatomic, strong) UIImageView *view2;
@property (nonatomic, strong) UIImageView *view3;
為了等下代碼更加方便操作,簡(jiǎn)化代碼,我們聲明一個(gè)設(shè)置view的方法,這里同樣用到SDWebImage獲取圖片。
// 創(chuàng)建 imageview ,需要frame 和 圖片網(wǎng)絡(luò)地址 的參數(shù)
- (UIImageView *)createImageViewWithFrame:(CGRect)frame imageUreStr:(NSString *)string {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.alpha = 0.f; // 透明度設(shè)置為0
[imageView sd_setImageWithURL:[NSURL URLWithString:string]];
[self.view addSubview:imageView];
return imageView;
}
接著我們創(chuàng)建一個(gè)方法,就叫 setGCD 吧。
- (void)setGCD {
NSString *img1 = @"http://o7mwf03sy.bkt.clouddn.com/1460334156212.jpg";
NSString *img2 = @"http://o7mwf03sy.bkt.clouddn.com/1460334134611.jpg";
NSString *img3 = @"http://o7mwf03sy.bkt.clouddn.com/1460334144112.jpg";
self.view1 = [self createImageViewWithFrame:CGRectMake(50, 50, 200, 100) imageUreStr:img1];
self.view2 = [self createImageViewWithFrame:CGRectMake(50, 200, 200, 100) imageUreStr:img2];
self.view3 = [self createImageViewWithFrame:CGRectMake(50, 350, 200, 100) imageUreStr:img3];
// 在子線程中完成下載 圖片1
[GCDQueue executeInGlobalQueue:^{
// 在主線程中更新UI
[GCDQueue executeInMainQueue:^{
NSLog(@"線程 1 開始執(zhí)行");
// 2秒動(dòng)畫顯示圖片
[UIView animateWithDuration:2.f animations:^{
self.view1.alpha = 1.f;
} completion:^(BOOL finished) {
NSLog(@"線程 1 執(zhí)行完畢");
}];
}];
}];
// 在子線程中完成下載 圖片2
[GCDQueue executeInGlobalQueue:^{
NSLog(@"線程 2 開始執(zhí)行");
// 在主線程中更新UI
[GCDQueue executeInMainQueue:^{
// 2秒動(dòng)畫顯示圖片
[UIView animateWithDuration:2.f animations:^{
self.view2.alpha = 1.f;
} completion:^(BOOL finished) {
NSLog(@"線程 2 執(zhí)行完畢");
}];
}];
}];
// 在子線程中完成下載 圖片3
[GCDQueue executeInGlobalQueue:^{
NSLog(@"線程 3 開始執(zhí)行");
// 在主線程中更新UI
[GCDQueue executeInMainQueue:^{
// 2秒動(dòng)畫顯示圖片
[UIView animateWithDuration:2.f animations:^{
self.view3.alpha = 1.f;
} completion:^(BOOL finished) {
NSLog(@"線程 3 執(zhí)行完畢");
}];
}];
}];
}
此時(shí)的輸出結(jié)果是:

可見三張圖片幾乎是同時(shí)開始執(zhí)行, 同時(shí)執(zhí)行完畢。 但是假如我們有一種需求,要三張圖片按順序一張一張的出現(xiàn),該怎么辦呢?
對(duì)了,前面介紹過,使用
GCDSemaphore信號(hào)量!創(chuàng)建 GCDSemaphore 并在響應(yīng)位置插入信號(hào)代碼
...
self.view3 = [self createImageViewWithFrame:CGRectMake(50, 350, 200, 100) imageUreStr:img3];
GCDSemaphore *semaphore = [[GCDSemaphore alloc] init];
// 在子線程中完成下載 圖片1
[GCDQueue executeInGlobalQueue:^{
// 在主線程中更新UI
[GCDQueue executeInMainQueue:^{
NSLog(@"線程 1 開始執(zhí)行");
// 2秒動(dòng)畫顯示圖片
[UIView animateWithDuration:2.f animations:^{
self.view1.alpha = 1.f;
} completion:^(BOOL finished) {
NSLog(@"線程 1 執(zhí)行完畢");
// 發(fā)送執(zhí)行完畢信號(hào)
[semaphore signal];
}];
}];
}];
// 在子線程中完成下載 圖片2
[GCDQueue executeInGlobalQueue:^{
// 阻塞執(zhí)行,等待消息
[semaphore wait];
NSLog(@"線程 2 開始執(zhí)行");
// 在主線程中更新UI
[GCDQueue executeInMainQueue:^{
// 2秒動(dòng)畫顯示圖片
[UIView animateWithDuration:2.f animations:^{
self.view2.alpha = 1.f;
} completion:^(BOOL finished) {
NSLog(@"線程 2 執(zhí)行完畢");
// 發(fā)送執(zhí)行完畢信號(hào)
[semaphore signal];
}];
}];
}];
// 在子線程中完成下載 圖片3
[GCDQueue executeInGlobalQueue:^{
// 阻塞執(zhí)行,等待消息
[semaphore wait];
NSLog(@"線程 3 開始執(zhí)行");
// 在主線程中更新UI
[GCDQueue executeInMainQueue:^{
// 2秒動(dòng)畫顯示圖片
[UIView animateWithDuration:2.f animations:^{
self.view3.alpha = 1.f;
} completion:^(BOOL finished) {
NSLog(@"線程 3 執(zhí)行完畢");
}];
}];
}];
}
這樣,在線程1執(zhí)行完畢后發(fā)送信號(hào),線程2接收線程1發(fā)送的信號(hào)再開始執(zhí)行,完畢之后再給線程3發(fā)送信號(hào),以此類推,就能實(shí)現(xiàn)按順序執(zhí)行線程。輸出結(jié)果如下:

三個(gè)線程的開始執(zhí)行和結(jié)束執(zhí)行的時(shí)間有了明顯差別,實(shí)現(xiàn)了我們想要的效果。
總結(jié)
到這里,我們基本能學(xué)到一下幾點(diǎn)有關(guān)GCD的實(shí)用知識(shí)點(diǎn)
- GCD串行隊(duì)列與并發(fā)隊(duì)列
- GCD延時(shí)執(zhí)行操作使用
- GCD線程組的操作使用
- GCD定時(shí)器的使用
- GCD信號(hào)量將異步操作轉(zhuǎn)變成同步操作的使用