GCD的基本使用
一、主隊列介紹
主隊列:是和主線程相關聯的隊列,主隊列是GCD自帶的一種特殊的串行隊列,放在主隊列中得任務,都會放到主線程中執行。
提示:如果把任務放到主隊列中進行處理,那麼不論處理函數是異步的還是同步的都不會開啟新的線程。
獲取主隊列的方式:
復制代碼 代碼如下:
dispatch_queue_t queue=dispatch_get_main_queue();
(1)使用異步函數執行主隊列中得任務,代碼示例:
復制代碼 代碼如下:
//
// YYViewController.m
// 12-GCD的基本使用(主隊列)
//
// Created by 孔醫己 on 14-6-25.
// Copyright (c) 2014年 itcast. All rights reserved.
//
#import "YYViewController.h"
@interface YYViewController ()
@end
復制代碼 代碼如下:
@implementation YYViewController
- (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]);
});
}
@end
執行效果:
(2)使用同步函數,在主線程中執行主隊列中得任務,會發生死循環,任務無法往下執行。示意圖如下:
二、基本使用
1.問題
任務1和任務2是在主線程執行還是子線程執行,還是單獨再開啟一個新的線程?
復制代碼 代碼如下:
//
// YYViewController.m
// 13-GCD基本使用(問題)
//
// Created by 孔醫己 on 14-6-25.
// Copyright (c) 2014年 itcast. All rights reserved.
//
#import "YYViewController.h"
@interface YYViewController ()
@end
復制代碼 代碼如下:
@implementation YYViewController
- (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]);
});
}
@end
打印結果:
2.開啟子線程,加載圖片
復制代碼 代碼如下:
//
// YYViewController.m
// 14-GCD基本使用(下載圖片)
//
// Created by 孔醫己 on 14-6-25.
// Copyright (c) 2014年 itcast. All rights reserved.
//
#import "YYViewController.h"
@interface YYViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
復制代碼 代碼如下:
@implementation YYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
//當手指觸摸屏幕的時候,從網絡上下載一張圖片到控制器的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];
});
}
@end
顯示效果:
打印結果:
要求使用GCD的方式,在子線程加載圖片完畢後,主線程拿到加載的image刷新UI界面。
復制代碼 代碼如下:
//
// YYViewController.m
// 14-GCD基本使用(下載圖片)
//
// Created by 孔醫己 on 14-6-25.
// Copyright (c) 2014年 itcast. All rights reserved.
//
#import "YYViewController.h"
@interface YYViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
復制代碼 代碼如下:
@implementation YYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
//當手指觸摸屏幕的時候,從網絡上下載一張圖片到控制器的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];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image=image;
//打印當前線程
NSLog(@"%@",[NSThread currentThread]);
});
});
}
@end
打印結果:
好處:子線程中得所有數據都可以直接拿到主線程中使用,更加的方便和直觀。
三、線程間通信
從子線程回到主線程
復制代碼 代碼如下:
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執⾏耗時的異步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主線程,執⾏UI刷新操作
});
});
GCD的常見用法
一、延遲執行
1.介紹
iOS常見的延時執行有2種方式
(1)調用NSObject的方法
復制代碼 代碼如下:
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒後再調用self的run方法
(2)使用GCD函數
復制代碼 代碼如下:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒後異步執行這裡的代碼...
});
2.說明
第一種方法,該方法在那個線程調用,那麼run就在哪個線程執行(當前線程),通常是主線程。
復制代碼 代碼如下:
[self performSelector:@selector(run) withObject:nil afterDelay:3.0];
說明:在3秒鐘之後,執行run函數
代碼示例:
復制代碼 代碼如下:
//
// YYViewController.m
// 01-GCD的常見使用(延遲執行)
//
// Created by apple on 14-6-25.
// Copyright (c) 2014年 itcase. All rights reserved.
//
#import "YYViewController.h"
@interface YYViewController ()
@end
復制代碼 代碼如下:
@implementation YYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"打印線程----%@",[NSThread currentThread]);
//延遲執行
//第一種方法:延遲3秒鐘調用run函數
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
}
-(void)run
{
NSLog(@"延遲執行----%@",[NSThread currentThread]);
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//在異步函數中執行
dispatch_queue_t queue = dispatch_queue_create("wendingding", 0);
dispatch_sync(queue, ^{
[self performSelector:@selector(test) withObject:nil afterDelay:1.0];
});
NSLog(@"異步函數");
}
-(void)test
{
NSLog(@"異步函數中延遲執行----%@",[NSThread currentThread]);
}
@end
說明:如果把該方法放在異步函數中執行,則方法不會被調用(BUG?)
第二種方法,
復制代碼 代碼如下:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//延遲執行的方法
});
說明:在5秒鐘之後,執行block中的代碼段。
參數說明:
什麼時間,執行這個隊列中的這個任務。
代碼示例:
復制代碼 代碼如下:
//
// YYViewController.m
// 02-GCD常見使用(延遲執行2)
//
// Created by apple on 14-6-25.
// Copyright (c) 2014年 itcase. All rights reserved.
//
#import "YYViewController.h"
@interface YYViewController ()
@end
復制代碼 代碼如下:
@implementation YYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"打印當前線程---%@", [NSThread currentThread]);
//延遲執行,第二種方式
//可以安排其線程(1),主隊列
dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
NSLog(@"主隊列--延遲執行------%@",[NSThread currentThread]);
});
//可以安排其線程(2),並發隊列
//1.獲取全局並發隊列
dispatch_queue_t queue1= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.計算任務執行的時間
dispatch_time_t when=dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
//3.會在when這個時間點,執行queue中的這個任務
dispatch_after(when, queue1, ^{
NSLog(@"並發隊列-延遲執行------%@",[NSThread currentThread]);
});
}
@end
延遲執行:不需要再寫方法,且它還傳遞了一個隊列,我們可以指定並安排其線程。
如果隊列是主隊列,那麼就在主線程執行,如果隊列是並發隊列,那麼會新開啟一個線程,在子線程中執行。
二、一次性代碼
1.實現一次性代碼
需求:點擊控制器只有第一次點擊的時候才打印。
實現代碼:
復制代碼 代碼如下:
//
// YYViewController.m
// 03-GCD常見使用(一次性代碼)
//
// Created by apple on 14-6-25.
// Copyright (c) 2014年 itcase. All rights reserved.
//
#import "YYViewController.h"
@interface YYViewController ()
@property(nonatomic,assign) BOOL log;
@end
@implementation YYViewController
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (_log==NO) {
NSLog(@"該行代碼只執行一次");
_log=YES;
}
}
@end
缺點:這是一個對象方法,如果又創建一個新的控制器,那麼打印代碼又會執行,因為每個新創建的控制器都有自己的布爾類型,且新創建的默認為NO,因此不能保證改行代碼在整個程序中只打印一次。
2.使用dispatch_once一次性代碼
使用dispatch_once函數能保證某段代碼在程序運行過程中只被執行1次
復制代碼 代碼如下:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執行1次的代碼(這裡面默認是線程安全的)
});
整個程序運行過程中,只會執行一次。
代碼示例:
復制代碼 代碼如下:
//
// YYViewController.m
// 03-GCD常見使用(一次性代碼)
//
// Created by apple on 14-6-25.
// Copyright (c) 2014年 itcase. All rights reserved.
//
#import "YYViewController.h"
@interface YYViewController ()
@property(nonatomic,assign) BOOL log;
@end
復制代碼 代碼如下:
@implementation YYViewController
//-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
//{
// if (_log==NO) {
// NSLog(@"該行代碼只執行一次");
// _log=YES;
// }
//}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"該行代碼只執行一次");
});
}
@end
效果(程序運行過程中,打印代碼只會執行一次):
三、隊列組
需求:從網絡上下載兩張圖片,把兩張圖片合並成一張最終顯示在view上。
1.第一種方法
代碼示例:
復制代碼 代碼如下:
//
// YYViewController.m
// 04-GCD基本使用(隊列組下載圖片)
//
// Created by apple on 14-6-25.
// Copyright (c) 2014年 itcase. All rights reserved.
//
#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 queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//獲取主隊列
// dispatch_queue_t queue= dispatch_get_main_queue();
// 圖片1:http://d.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=2b9a12172df5e0fefa1581533d095fcd/cefc1e178a82b9019115de3d738da9773912ef00.jpg
// 圖片2:http://h.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=f47fd63ca41ea8d39e2f7c56f6635b2b/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg
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
顯示效果:
打印查看:
問題:這種方式的效率不高,需要等到圖片1.圖片2都下載完成後才行。
提示:使用隊列組可以讓圖片1和圖片2的下載任務同時進行,且當兩個下載任務都完成的時候回到主線程進行顯示。
2.使用隊列組解決
步驟:
創建一個組
開啟一個任務下載圖片1
開啟一個任務下載圖片2
同時執行下載圖片1\下載圖片2操作
等group中的所有任務都執行完畢, 再回到主線程執行其他操作
代碼示例
復制代碼 代碼如下:
//
// YYViewController.m
// 04-GCD基本使用(隊列組下載圖片)
//
// Created by apple on 14-6-25.
// Copyright (c) 2014年 itcase. All rights reserved.
//
#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:http://d.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=2b9a12172df5e0fefa1581533d095fcd/cefc1e178a82b9019115de3d738da9773912ef00.jpg
// 圖片2:http://h.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=f47fd63ca41ea8d39e2f7c56f6635b2b/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg
//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]);
});
//同時執行下載圖片1\下載圖片2操作
//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]);
});
}
-(void)download2image
{
//獲取全局並發隊列
// dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//獲取主隊列
// dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_async(global_quque, ^{
//下載圖片1
UIImage *image1= [self imageWithUrl:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];
NSLog(@"圖片1下載完成---%@",[NSThread currentThread]);
//下載圖片2
UIImage *image2= [self imageWithUrl:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.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(0, 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
打印查看(同時開啟了兩個子線程,分別下載圖片):
2.補充說明
有這麼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(), ^{
// 等前面的異步操作都執行完畢後,回到主線程...
});