ios-GCD 核心

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;

}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容