GCD,全稱是 Grand Central Dispatch,純 C 語言,提供了非常多強大的函數. 是蘋果公司為多核的并行運算提出的解決方案, 會自動利用更多的CPU內核(比如雙核、四核), 會自動管理線程的生命周期(創建線程、調度任務、銷毀線程), 程序員只需要告訴 GCD 想要執行什么任務,不需要編寫任何線程管理代碼.
核心概念
1. 將任務添加到隊列,并且指定執行任務的函數
2. 任務使用block封裝
? ? ? ? 任務的block沒有參數也沒有返回值
3. 執行任務的函數
? ? ? ?異步dispatch_async
? ? ? ? ? ?不用等待當前語句執行完畢,就可以執行下一條語句
? ? ? ? ? ?會開啟線程執行block的任務
? ? ? ? ? ?異步是多線程的代名詞
? ? ? ?同步dispatch_sync
? ? ? ? ? ? ?必須等待當前語句執行完畢,才會執行下一條語句
? ? ? ? ? ? ?不會開啟線程
? ? ? ? ? ? 在當前執行block的任務
4. 隊列- 系統以先進先出的方式調度隊列中的任務執行
? ? ? ? 串行隊列
? ? ? ? ? ? ? 一次只能"調度"一個任務?
? ? ? ? ? ? ?dispatch_queue_create("cn.itcast.queue", NULL);
? ? ? ?并發隊列
? ? ? ? ? ? ? 一次可以"調度"多個任務
? ? ? ? ? ? ?dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);
? ? ? ?主隊列
? ? ? ? ? ? 專門用來在主線程上調度任務的隊列
? ? ? ? ? ? 不會開啟線程
? ? ? ? ? ? ? 在主線程空閑時才會調度隊列中的任務在主線程執行
? ? ? ? ? ? ? dispatch_get_main_queue();
? ? ? 全局隊列
? ? ? ? ? ? 為了方便程序員的使用,蘋果提供了全局隊列
dispatch_get_global_queue(0, 0)
? ? ? ? ? ? 全局隊列是一個并發隊列
? ? ? ? ? ? 在使用多線程開發時,如果對隊列沒有特殊需求,在執行異步任務時,可以直接使 ? ? ? ? ? 用全局隊列
小結:
開不開線程由執行任務的函數決定
? ? ? ? ?異步開,異步是多線程的代名詞
? ? ? ? ?同步不開
開幾條線程由隊列決定
? ? ? ? 串行隊列開一條線程
? ? ? ? 并發隊列開多條線程,具體能開的線程數量由底層線程池決定
? ? ? ? ? ? ? ? iOS 8.0 之后,GCD 能夠開啟非常多的線程
? ? ? ? ? ? ? ? iOS 7.0 以及之前,GCD 通常只會開啟 5~6 條線程
- 隊列的選擇
多線程的目的:將耗時的操作放在后臺執行!
串行隊列,只開一條線程,所有任務順序執行
? ? ? ? ?如果任務有先后執行順序的要求
? ? ? ? ?效率低 -> 執行慢 -> "省電"
? ? ? ? 有的時候,用戶其實不希望太快!例如使用 3G 流量,"省錢"
并發隊列,會開啟多條線程,所有任務不按照順序執行
? ? ? ?如果任務沒有先后執行順序的要求
? ? ? ?效率高 -> 執行快 -> "費電"
? ? ? ?WIFI,包月
實際開發中,線程數量如何決定?
? ? ? ?WIFI 線程數6條
? ? ? ?3G / 4G 移動開發的時候,2~3條,再多會費電費錢!
全局隊列 和 主隊列
全局隊列
為了方便程序員的使用,蘋果提供了全局隊列dispatch_get_global_queue(0, 0)
全局隊列是一個并發隊列,馬上會講
在使用多線程開發時,如果對隊列沒有特殊需求,在執行異步任務時,可以直接使用全局隊列
主隊列
為了方便線程間通訊,異步執行完網絡任務,在主線程更新 UI
蘋果提供了主隊列dispatch_get_main_queue()
主隊列專門用于在主線程上調度任務執行
異步執行任務
- (void)gcdDemo1 {
// 1. 全局隊列
dispatch_queue_tqueue = dispatch_get_global_queue(0,0);
// 2. 任務
dispatch_block_t task = ^ {
NSLog(@"hello gcd %@", [NSThreadcurrentThread]);
? ? };
// 3. 將任務添加到隊列,并且指定異步執行
dispatch_async(queue, task);
}
注意:如果等待時間長一些,會發現線程的number發生變化,由此可以推斷gcd 底層線程池的工作
精簡代碼
模擬循環添加 10 個異步下載圖像的人物
// 精簡代碼
- (void)gcdDemo2 {
for(NSInteger i =0; i <10; i++) {
? ? ? ? [NSThread sleepForTimeInterval:0.5];
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSLog(@"下載圖像 %zd %@", i, [NSThreadcurrentThread]);
? ? ? ? });
? ? }
}
NSThread 實現等價代碼對比
#pragma mark - NSThread 代碼
- (void)threadDemo {
for(NSInteger i =0; i <10; i++) {
? ? ? ? [NSThread sleepForTimeInterval:0.5];
? ? ? ? [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:@(i)];
? ? }
}
- (void)downloadImage:(id)obj {
NSLog(@"下載圖像 %@ %@", obj, [NSThreadcurrentThread]);
}
與NSThread的對比
1.所有的代碼寫在一起的,讓代碼更加簡單,易于閱讀和維護
? ? ? ? ?NSThread通過@selector指定要執行的方法,代碼分散
? ? ? ? ?GCD通過block指定要執行的代碼,代碼集中
2.使用GCD不需要管理線程的創建/銷毀/復用的過程!程序員不用關心線程的生命周期
3.如果要開多個線程NSThread必須實例化多個線程對象
4.NSThread靠NSObject的分類方法實現的線程間通訊,GCD靠block
線程間通訊
#pragma mark - 線程間通訊
- (void)gcdDemo3 {
dispatch_async(dispatch_get_global_queue(0,0), ^{
// 異步下載圖像
NSLog(@"下載圖像 %@", [NSThread currentThread]);
// 主線程更新
UIdispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主線程更新 UI %@", [NSThread currentThread]);
? ? ? ? });
? ? });
}
以上代碼是GCD最常用代碼組合!
網絡下載圖片
- (void)viewDidLoad {
? ? [super viewDidLoad];
// 1. 準備
URLNSString*urlString =@"http://image.tianjimedia.com/uploadImages/2011/286/8X76S7XD89VU.jpg";
NSURL*url = [NSURLURLWithString:urlString];
// 2. 下載圖像
dispatch_async(dispatch_get_global_queue(0,0), ^{
// 1> 所有從網絡返回的都是二進制數據
NSData*data = [NSData dataWithContentsOfURL:url];
// 2> 將二進制數據轉換成 image
UIImage*image = [UIImage imageWithData:data];
// 3> 主線程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 1> 設置圖像
_imageView.image= image;
// 2> 調整大小
[_imageView sizeToFit];
// 3> 設置 contentSize
_scrollView.contentSize= image.size;
? ? ? ? });
? ? });
}
串行隊列
特點
以先進先出的方式,順序調度隊列中的任務執行
無論隊列中所指定的執行任務函數是同步還是異步,都會等待前一個任務執行完成后,再調度后面的任務
隊列創建
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue",NULL);
串行隊列 同步執行
/// 串行隊列 - 同步執行
/// 提問:是否開線程?是否順序執行?come here 的位置?
/// 回答:不會開線程/順序執行/最后
- (void)gcdDemo1 {
// 1. 串行隊列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_SERIAL);
// 2. 添加同步執行的任務
for(NSInteger i =0; i <10; i++) {
dispatch_sync(queue, ^{
? ? ? ? ? ? [NSThread sleepForTimeInterval:0.5];
NSLog(@"%@ %zd", [NSThread currentThread], i);
? ? ? ? });
? ? }
NSLog(@"come here");
}
串行隊列 異步執行
/// 串行隊列 - 異步執行
/// 提問:是否開線程?是否順序執行?come here 的位置?
/// 回答:會開一條線程/順序執行/不確定
- (void)gcdDemo2 {
// 1. 串行隊列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_SERIAL);
// 2. 添加異步執行的任務
for(NSInteger i =0; i <10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %zd", [NSThread currentThread], i);
? ? ? ? });
? ? }
NSLog(@"come here");
}
并發隊列
特點
以先進先出的方式,并發調度隊列中的任務執行
如果當前調度的任務是同步執行的,會等待任務執行完成后,再調度后續的任務
如果當前調度的任務是異步執行的,同時底層線程池有可用的線程資源,會再新的線程調度后續任務的執行
隊列創建
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
并發隊列 異步執行
/// 并發隊列 - 異步執行
/// 提問:是否開線程?是否順序執行?come here 的位置?
/// 回答:會開線程(取決于底層線程池可用資源)/不是順序執行/不確定
- (void)gcdDemo2 {
// 1. 并發隊列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);
// 2. 添加異步執行的任務
for(NSInteger i =0; i <10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %zd", [NSThread currentThread], i);
? ? ? ? });
? ? }
NSLog(@"come here");
}
并發隊列 同步執行
/// 并發隊列 - 同步執行
/// 提問:是否開線程?是否順序執行?come here 的位置?
/// 回答:不開線程/順序執行/最后
- (void)gcdDemo1 {
// 1. 并發隊列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);
// 2. 添加同步執行的任務
for(NSInteger i =0; i <10; i++) {
dispatch_sync(queue, ^{
? ? ? ? ? ? [NSThread sleepForTimeInterval:0.5];
NSLog(@"%@ %zd", [NSThread currentThread], i);?
? ? ? });
? ? }
NSLog(@"come here");
}
主隊列
特點
專門用來在主線程上調度任務的隊列
不會開啟線程
以先進先出的方式,在主線程空閑時才會調度隊列中的任務在主線程執行
如果當前主線程正在有任務執行,那么無論主隊列中當前被添加了什么任務,都不會被調度
隊列獲取
主隊列是負責在主線程調度任務的
會隨著程序啟動一起創建
主隊列只需要獲取不用創建
dispatch_queue_t queue = dispatch_get_main_queue();
主隊列,異步執行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {
? ? [self gcdDemo1];
? ? [NSThread sleepForTimeInterval:1.0];
NSLog(@"over");}
/// 主隊列異步
/// 提問:是否開線程?是否順序執行?come here 的位置?
/// 回答:不開/順序/最前面
- (void)gcdDemo1 {
// 1. 主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2. 異步任務
for(NSInteger i =0; i <10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@", [NSThread currentThread]);
? ? ? ? });
? ? }
NSLog(@"come here");
}
在主線程空閑時才會調度主隊列中的任務在主線程執行
主隊列,同步執行
/// 主隊列同步
- (void)gcdDemo2 {
NSLog(@"begin");
// 1. 主隊列
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello");?
? });
NSLog(@"end");
}
主隊列和主線程相互等待會造成死鎖
全局隊列
是系統為了方便程序員開發提供的,其工作表現與并發隊列一致
全局隊列 & 并發隊列的區別
全局隊列
沒有名稱
無論 MRC & ARC 都不需要考慮釋放
日常開發中,建議使用"全局隊列"
并發隊列
有名字,和NSThread的name屬性作用類似
如果在 MRC 開發時,需要使用dispatch_release(q);釋放相應的對象
dispatch_barrier必須使用自定義的并發隊列
開發第三方框架時,建議使用并發隊列
全局隊列 異步任務
/**
提問:是否開線程?是否順序執行?come here 的位置?
*/
- (void)gcdDemo8 {
// 1. 隊列
dispatch_queue_t q = dispatch_get_global_queue(0,0);
// 2. 執行任務
for(int i =0; i <10; ++i) {
dispatch_async(q, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
? ? ? ? });
? ? }
NSLog(@"come here");
}
運行效果與并發隊列相同
參數
服務質量(隊列對任務調度的優先級)/iOS 7.0 之前,是優先級
iOS 8.0(新增,暫時不能用,今年年底)
QOS_CLASS_USER_INTERACTIVE0x21, 用戶交互(希望最快完成-不能用太耗時的操作)
QOS_CLASS_USER_INITIATED0x19, 用戶期望(希望快,也不能太耗時)
QOS_CLASS_DEFAULT0x15, 默認(用來底層重置隊列使用的,不是給程序員用的)
QOS_CLASS_UTILITY0x11, 實用工具(專門用來處理耗時操作!)
QOS_CLASS_BACKGROUND0x09, 后臺
QOS_CLASS_UNSPECIFIED0x00, 未指定,可以和iOS 7.0 適配
iOS 7.0
DISPATCH_QUEUE_PRIORITY_HIGH2 高優先級
DISPATCH_QUEUE_PRIORITY_DEFAULT0 默認優先級
DISPATCH_QUEUE_PRIORITY_LOW(-2) 低優先級
DISPATCH_QUEUE_PRIORITY_BACKGROUNDINT16_MIN 后臺優先級
為未來保留使用的,應該永遠傳入0
結論:如果要適配 iOS 7.0 & 8.0,使用以下代碼:dispatch_get_global_queue(0, 0);
單例
有的時候,在程序開發中,有些代碼只想從程序啟動就只執行一次,典型的應用場景就是“單例”
單例的特點
在內存中只有一個實例
提供一個全局的訪問點
提示:單例的使用在 iOS 中非常普遍,以下代碼在很多公司的面試中,都要求能夠手寫出來
- (void)once {
static dispatch_once_t onceToken;
NSLog(@"%zd", onceToken);
// onceToken == 0 的時候執行 block 中的代碼
// block 中的代碼是同步執行的
dispatch_once(&onceToken, ^{
NSLog(@"執行了");
? ? });
NSLog(@"come here");
}
dispatch 內部也有一把鎖,是能夠保證"線程安全"的!而且是蘋果公司推薦使用的
以下代碼用于測試多線程的一次性執行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {
for(NSInteger i =0; i <10; i++) {
dispatch_async(dispatch_get_global_queue(0,0), ^{
? ? ? ? ? ? [self once];
? ? ? ? });?
? }
}
單例的特點
在內存中只有一個實例
提供一個全局的訪問點
懶漢式單例實現
所謂懶漢式單例,表示在使用時才會創建
@implementation NetworkTools
+ (instancetype)sharedTools {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
? ? ? ? instance = [[self alloc] init];
? ? });
return instance;
}
@end
面試時只要實現上面sharedTools方法即可
餓漢式單例實現
所謂餓漢式單例,表示盡早地創建單例實例,可以利用initialize方法建立單例
static id instance;
/// initialize 會在類第一次被使用時調用
/// initialize 方法的調用是線程安全的
+ (void)initialize {
NSLog(@"創建單例");
? ? instance = [[self alloc] init];
}
+ (instancetype)sharedTools {
return instance;
}