GCD的基本使用

weixin_33976072發表於2016-09-20

一、主佇列介紹
主佇列:是和主執行緒相關聯的佇列,主佇列是GCD自帶的一種特殊的序列佇列,放在主佇列中得任務,都會放到主執行緒中執行。
提示:如果把任務放到主佇列中進行處理,那麼不論處理函式是非同步的還是同步的都不會開啟新的執行緒。獲取主佇列的方式:

程式碼如下:
dispatch_queue_t queue=dispatch_get_main_queue();

(1)使用非同步函式執行主佇列中得任務,程式碼示例:

- (void)viewDidLoad{   
     [super viewDidLoad];        
    //列印主執行緒     
    NSLog(@"列印主執行緒--%@", [NSThread mainThread]);       
    //1.獲取主佇列    
    dispatch_queue_t queue=dispatch_get_main_queue();    
    //2.把任務新增到主佇列中執行    
    dispatch_async(queue, ^{        
        NSLog(@"使用非同步函式執行主佇列中的任務1--%@",[NSThread currentThread]);    
    });    
    dispatch_async(queue, ^{        
        NSLog(@"使用非同步函式執行主佇列中的任務2--%@",[NSThread currentThread]);    
    });    
    dispatch_async(queue, ^{       
        NSLog(@"使用非同步函式執行主佇列中的任務3--%@",[NSThread currentThread]);    
    });
}```
執行結果: 
![結果](http://upload-images.jianshu.io/upload_images/1803308-ef60529bbf50f770.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

(2)使用同步函式,在主執行緒中執行主佇列中得任務,會發生死迴圈,任務無法往下執行。
***

**二、基本使用**
1.問題 
任務1和任務2是在主執行緒執行還是子執行緒執行,還是單獨再開啟一個新的執行緒?
  • (void)viewDidLoad{
    [super viewDidLoad];
    //開啟一個後臺執行緒,呼叫執行test方法
    [self performSelectorInBackground:@selector(test) withObject:nil];
    }
    -(void)test{
    NSLog(@"當前執行緒---%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //非同步函式
    dispatch_async(queue, ^{
    NSLog(@"任務1所在的執行緒----%@",[NSThread currentThread]);
    });
    //同步函式
    dispatch_sync(queue, ^{
    NSLog(@"任務2所在的執行緒----%@",[NSThread currentThread]);
    });
    }
列印結果: 

![結果](http://upload-images.jianshu.io/upload_images/1803308-7dc7fbf42dd2d73f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2.開啟子執行緒,載入圖片

//當手指觸控螢幕的時候,從網路上下載一張圖片到控制器的view上顯示
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//1.獲取一個全域性序列佇列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.把任務新增到佇列中執行
dispatch_async(queue, ^{
//列印當前執行緒
NSLog(@"%@",[NSThread currentThread]);
//3.從網路上下載圖片
NSURL *urlstr=[NSURL URLWithString:@"http://h.hiphotos.baidu.com/baike/w%3D268/sign=30b3fb747b310a55c424d9f28f444387/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg"];
NSData *data=[NSData dataWithContentsOfURL:urlstr];
UIImage *image=[UIImage imageWithData:data];
//提示
NSLog(@"圖片載入完畢");
//4.回到主執行緒,展示圖片
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
});
}

***
####* 執行緒間的通訊
(要求使用GCD的方式,在子執行緒載入圖片完畢後,主執行緒拿到載入的image重新整理UI介面。)

//4.回到主執行緒,展示圖片

NSObject回到主執行緒的方法

//[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];

GCD回到主執行緒的方法

dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image=image;
//列印當前執行緒
NSLog(@"%@",[NSThread currentThread]);
});

***
#### * GCD的常見用法
**一、延遲執行**
(1)呼叫NSObject的方法

[self performSelector:@selector(run) withObject:nil afterDelay:2.0];

//2秒後再呼叫self的run方法
//該方法在那個執行緒呼叫,那麼run就在哪個執行緒執行(當前執行緒),通常是主執行緒。```

(2)使用GCD函式

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒後非同步執行這裡的程式碼...
});```
延遲執行:不需要再寫方法,且它還傳遞了一個佇列,我們可以指定並安排其執行緒。
如果佇列是主佇列,那麼就在主執行緒執行,如果佇列是併發佇列,那麼會新開啟一個執行緒,在子執行緒中執行。

**二、一次性程式碼**
1.使用`dispatch_once`一次性程式碼
使用`dispatch_once`函式能保證某段程式碼在程式執行過程中只被執行1次

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{
// 只執行1次的程式碼(這裡面預設是執行緒安全的)
});


**三、佇列組**
需求:從網路上下載兩張圖片,把兩張圖片合併成一張最終顯示在view上。
1.第一種方法
程式碼示例:

import "YYViewController.h"

//巨集定義全域性併發佇列

define global_quque dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

//巨集定義主佇列

define main_queue dispatch_get_main_queue()

@interface YYViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView1;
@property (weak, nonatomic) IBOutlet UIImageView *imageView2;
@property (weak, nonatomic) IBOutlet UIImageView *imageView3;
@end
@implementation YYViewController

  • (void)viewDidLoad{
    [super viewDidLoad];
    }

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//獲取全域性併發佇列
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//獲取主佇列
dispatch_queue_t main_queue = dispatch_get_main_queue();

dispatch_async(global_quque, ^{        
    //下載圖片1       
    UIImage *image1= [self imageWithUrl:@"http://d.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=2b9a12172df5e0fefa1581533d095fcd/cefc1e178a82b9019115de3d738da9773912ef00.jpg"];        
    NSLog(@"圖片1下載完成---%@",[NSThread currentThread]);            
    //下載圖片2       
    UIImage *image2= [self imageWithUrl:@"http://h.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=f47fd63ca41ea8d39e2f7c56f6635b2b/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg"];        
    NSLog(@"圖片2下載完成---%@",[NSThread currentThread]);                
    //回到主執行緒顯示圖片        
    dispatch_async(main_queue, ^{             
        NSLog(@"顯示圖片---%@",[NSThread currentThread]);            
        self.imageView1.image=image1;            
        self.imageView2.image=image2;            
        //合併兩張圖片            
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);            
        [image1 drawInRect:CGRectMake(0, 0, 100, 100)];            
        [image2 drawInRect:CGRectMake(100, 0, 100, 100)]; 
        self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext();            
        //關閉上下文            
        UIGraphicsEndImageContext();               
        NSLog(@"圖片合併完成---%@",[NSThread currentThread]);       
 });        

});
}

//封裝一個方法,傳入一個url引數,返回一張網路上下載的圖片
-(UIImage *)imageWithUrl:(NSString *)urlStr{
NSURL *url=[NSURL URLWithString:urlStr];
NSData *data=[NSData dataWithContentsOfURL:url];
UIImage *image=[UIImage imageWithData:data];
return image;
}
@end```
列印結果:

1803308-e2cd68cab44417aa.png
結果.png

問題:這種方式的效率不高,需要等到圖片1.圖片2都下載完成後才行。
提示:使用佇列組可以讓圖片1和圖片2的下載任務同時進行,且當兩個下載任務都完成的時候回到主執行緒進行顯示。

2.使用佇列組解決
步驟:
建立一個組
開啟一個任務下載圖片1
開啟一個任務下載圖片2
同時執行下載圖片1\下載圖片2操作
等group中的所有任務都執行完畢, 再回到主執行緒執行其他操作

#import "YYViewController.h"
//巨集定義全域性併發佇列
#define global_quque    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
//巨集定義主佇列
#define main_queue       dispatch_get_main_queue()
@interface YYViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView1;
@property (weak, nonatomic) IBOutlet UIImageView *imageView2;
@property (weak, nonatomic) IBOutlet UIImageView *imageView3;
@end
@implementation YYViewController
- (void)viewDidLoad{    
[super viewDidLoad];}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    //1.建立一個佇列組        
    dispatch_group_t group = dispatch_group_create();
    //2.開啟一個任務下載圖片1    
    __block UIImage *image1=nil;    
    dispatch_group_async(group, global_quque, ^{        
        image1= [self imageWithUrl:@"http://d.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=2b9a12172df5e0fefa1581533d095fcd/cefc1e178a82b9019115de3d738da9773912ef00.jpg"];            
        NSLog(@"圖片1下載完成---%@",[NSThread currentThread]);   
    });
     //3.開啟一個任務下載圖片2    
     __block UIImage *image2=nil;    
    dispatch_group_async(group, global_quque, ^{       
        image2= [self imageWithUrl:@"http://h.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=f47fd63ca41ea8d39e2f7c56f6635b2b/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg"];            
        NSLog(@"圖片2下載完成---%@",[NSThread currentThread]);    
    });
    
    //4.等group中的所有任務都執行完畢, 再回到主執行緒執行其他操作    
    dispatch_group_notify(group,main_queue, ^{        
         NSLog(@"顯示圖片---%@",[NSThread currentThread]); 
         self.imageView1.image=image1;        
         self.imageView2.image=image2;                
         //合併兩張圖片        
         //注意最後一個引數是浮點數(0.0),不要寫成0。        
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);        
        [image1 drawInRect:CGRectMake(0, 0, 100, 100)];        
        [image2 drawInRect:CGRectMake(100, 0, 100, 100)]; 
        self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext();        
        //關閉上下文        
        UIGraphicsEndImageContext();               
         NSLog(@"圖片合併完成---%@",[NSThread currentThread]);   
    });
}

補充說明
有這麼1種需求:
首先:分別非同步執行2個耗時的操作
其次:等2個非同步操作都執行完畢後,再回到主執行緒執行操作

如果想要快速高效地實現上述需求,可以考慮用佇列組

# 程式碼如下
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(), ^{
    // 等前面的非同步操作都執行完畢後,回到主執行緒...
});```