iOS-----使用GCD實現多線程,ios-----gcd多線程
使用GCD實現多線程GCD的兩個核心概念如下:
隊列 |
隊列負責管理開發人員提交的任務,GCD隊列始終以FIFO(先進先出)的方式來處理任務---但 由於任務的執行時間並不相同,因此先處理的任務並一定先結束。隊列既可是串列隊列,也可是並發隊列則可同時處理多個任務,因此將會有多個任務並發執行。 隊列底層會維護一個線程池來處理使用者提交的任務,線程池的作用就是執行隊列管理的任務。串列隊列底層的線程池只要維護一個線程即可,並發隊列的底層則需要維護多個線程。 |
任務 |
任務就是使用者提交給隊列的工作單元,這些任務將會提交給隊列底層維護的線程池執行,因此這些任務會以多線程的方式執行。 |
使用GCD只要遵守兩個步驟即可 |
1. |
建立隊列 |
2. |
將任務提交給隊列 |
|
|
|
建立隊列
GCD的隊列可分為兩種 |
串列隊列 |
串列隊列底層的線程只要一個線程,因此只提供一個線程用來執行任務,所以後一個任必須等到前一個任務執行結束才能開始執行 |
並發隊列 |
線程池提供多個線程來執行任務,所以可以按FIFO(先進先出)的順序並發啟動、執行多個並發任務。 |
函數 |
|
涉及一個dispatch_queue_t,這種類型就代表一個隊列。 |
程式可以建立如下幾種隊列 |
|
擷取系統預設的全域並發隊列可通過如下程式碼完成: dispatch_queue_t queue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 擷取系統主線程關聯的串列隊列可通過如下程式碼完成: dispatch_queue_t queue = dispatch_get_main_queue(); 如果將任務提交給主線程關聯的串列隊列,那麼就相當於直接在程式主線程中去執行該任務。 建立串列隊列可通過如下程式碼完成: dispatch_queue_t queue = dispatch_queue_create(“LCiOS.queue”, DISPATCH_QUEUE_SERIAL); 如果將多個任務提交給串列隊列,多個任務只能按順序執行,必須等前一個任務完成後,才能開始執行後一個任務。 建立並發隊列可通過如下程式碼完成: dispatch_queue_t queue = dispacth_queue_create(“LCiOS.queue” , DISPATCH_QUEUE_CONCURRENT); 如果將多個任務提交給並發隊列,並發隊列可以按FIFO(先進先出)的順序啟動多個並發執行的任務,由於任務的耗時間長度短並不相同,因此後提交的任務完全可能先完成。 得到隊列之後,接下來就可以將任務提交給隊列,並由隊列底層管理的線程池來執行這些任務。 |
|
|
|
非同步提交任務
iOS提供了如下函數來向隊列提交任務。下面這些函數很多都有兩個版本:一個接收代碼塊作為參數的版本,
一個接收函數作為參數的版本----其中接收函數作為參數的函數名最後多了_f尾碼,而且會多一個參數,用於向函數傳入應用程式定義的上下文。
代 碼 片 段 |
1 ViewController.m 2 3 @implementation ViewController 4 5 // 定義2個隊列 6 7 dispatch_queue_t serialQueue; 8 9 dispatch_queue_t concurrentQueue; 10 11 - (void)viewDidLoad 12 13 { 14 15 [super viewDidLoad]; 16 17 // 建立串列隊列 18 19 serialQueue = dispatch_queue_create(“LCiOS.queue”,DISPATCH_QUEUE_SERIAL); 20 21 // 建立並發隊列 22 23 concurrentQueue = dispatch_queue_create(“LCiOS.queue”,DISPATCH_QUEUE_CONCURRENT); 24 25 } 26 27 - (IBAction)serial:(id)sender 28 29 { 30 31 // 依次將兩個代碼塊提交給串列隊列 32 33 // 必須等到第1個代碼塊完成後,才能執行第2個代碼塊 34 35 dispatch_async(serialQueue, ^(void) 36 37 { 38 39 for(int i = 0; i < 100; i++) 40 41 { 42 43 NSLog(@”%@====%d”, [NSThread currentThread], i); 44 45 } 46 47 }); 48 49 dispatch_async(serialQueue, ^(void) 50 51 { 52 53 for(int i = 0; i < 100; i++) 54 55 { 56 57 NSLog(@”%@-----%d”, [NSThread currentThread], i); 58 59 } 60 61 }); 62 63 } 64 65 - (IBAction)concurrent:(id)sender 66 67 { 68 69 // 依次將兩個代碼塊提交給並發隊列 70 71 // 兩個代碼塊可以並發執行 72 73 dispatch_async(concurrentQueue, ^(void) 74 75 { 76 77 for(int i = 0; i < 100; i ++) 78 79 { 80 81 NSLog(@”%@====%d”, [NSThread currentThread] , i); 82 83 } 84 85 }); 86 87 dispatch_async(concurrentQueue, ^(void) 88 89 { 90 91 for(int i = 0; i < 100; i ++) 92 93 { 94 95 NSLog(@”%@-----%d”, [NSThread currentThread] , i); 96 97 } 98 99 });100 101 }102 103 @end |
說 明 |
上面程式中的兩行粗體字代碼建立了兩個隊列,其中第1個隊列是串列隊列,第2個隊列是並發隊列。接下來程式實現了serial:和concurrent:兩個事件處理方法,在serial:方法中使用dispatch_async()函數向串列隊列以非同步方式提交兩個代碼塊,在concurrent:方法中使用dispatch_async()函數向並發隊列以非同步方式提交兩個代碼塊。 編譯、運行該程式,如果使用者單擊的按鈕控製程序向串列隊列提交兩個代碼塊,將可以在控制台看到如所示的輸出。 如果 如果使用者單擊的按鈕控製程序向並發隊列提交兩個代碼塊,將可以在控制台看到如所示的輸出 |
使用G CD下載圖片 |
1 ViewController.m 2 3 @implementation ViewController 4 5 - (void)viewDidLoad 6 7 { 8 9 [super viewDidLoad];10 11 }12 13 - (IBAction)downImag:(id)sender14 15 {16 17 // 將代碼塊提交給系統的全域並發隊列18 19 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH , 0) , ^(void){20 21 NSString* url = @” http://images.cnblogs.com/cnblogs_com/congli0220/752817/o_%e4%b8%8b%e8%bd%bd.jpg”;22 23 // 從網路擷取資料24 25 NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];26 27 // 將網路資料初始化為UIImage對象28 29 UIImage *image = [[UIImage alloc] initWithData:data];30 31 if(image != nil)32 33 {34 35 // 將代碼塊提交給主線程關聯的隊列,該代碼將會由主線程完成36 37 dispatch_async(dispatch_get_main_queue(), ^{38 39 self.iv.image = image;40 41 });// 142 43 }44 45 else46 47 {48 49 NSLog(@”---下載圖片出現錯誤---”);50 51 }52 53 });54 55 }56 57 @end |
說明 |
該程式中的代碼會將代碼塊提交給系統預設的全域並發隊列,該代碼塊就會負責從網路下載圖片。由於該代碼塊同樣會在多線程中執行,因此程式的“1”號代碼處再次使用了dispatch_async()函數將更新介面上UI控制項的代碼交給主線程執行. |
同步提交任務
dispatch_sync()函數則會以同步方式提交代碼塊,該函數必須等到代碼塊執行結束才會返回.如果程式使用該函數先後提交了兩個代碼塊(即使提交給並發隊列),也必須等第1個任務完成後才會開始執行第2個任務.
例如如下樣本的視圖控制器類的實現部分代碼 |
代 碼 片 段 |
1 ViewController.m 2 3 @implementation ViewController 4 5 - (void)viewDidLoad 6 7 { 8 9 [super viewDidLoad];10 11 }12 13 - (IBAction)clicked:(id)sender14 15 {16 17 // 以同步方式先後提交兩個代碼塊18 19 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){20 21 for(int i = 0 ; i < 100; i++)22 23 {24 25 NSLog(@”%@====%d”, [NSThread currentThread] , i);26 27 [NSThread sleepForTimeInterval:0.1];28 29 }30 31 });32 33 // 必須等第1次提交的代碼塊執行完成後,dispatch_sync()函數才會返回34 35 // 程式才會執行到這裡,才能提交第2個代碼塊36 37 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^(void){38 39 for(int i = 0 ; i < 100; i ++)40 41 {42 43 NSLog(@”%@------%d”, [NSThread currentThread] , i);44 45 [NSThread sleepForTimeInterval:0.1];46 47 }48 49 });50 51 }52 53 @end |
說明 |
上面程式使用dispatch_sync()函數以同步方式提交代碼塊,該函數必須等到所提交的代碼塊執行完成後才會返回,因此該函數雖然啟動另外的線程來執行代碼塊,但它依然會阻塞主線程. 上面程式先後兩次使用dispatch_sync()函數來提交代碼塊,因此程式必須等到第1次使用dispatch_sync()函數提交的代碼塊執行完成後,該函數才會返回,程式才會執行第2次提交. 只有等到兩次提交的代碼塊都執行完成後,clicked:事件處理方法才能返回----表明事件響應執行完成.如果使用者單擊激發該事件的按鈕,該按鈕將會一直處於高亮狀態,直到兩個代碼塊執行完成 |
多次執行的任務
dispatch_apply()函數將控制提交的代碼塊重複執行多次,如果該代碼塊被提交給並發隊列,系統可以使用多個線程並發執行同一個代碼塊.
下面樣本程式在介面上包含了一個按鈕,當使用者單擊該按鈕時將會使用dispatch_apply()函數將代碼塊提交給並發隊列,並控制該代碼塊執行多次.
下面是該樣本的視圖控制器類的實現部分代碼.
代 碼 片 段 |
1 ViewController.m 2 3 @implementation ViewController 4 5 - (void)viewDidLoad 6 7 { 8 9 [super viewDidLoad];10 11 }12 13 - (IBAction)clicked:(id)sender14 15 {16 17 // 控制碼執行5次18 19 dispatch_apply(5 , dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)20 21 // time形參代表當前正在執行第幾次22 23 ,^(size_t time)24 25 {26 27 NSLog(@===執行【%lu】次===%@”, time , [NSThread currentThread]);28 29 });30 31 }32 33 @end |
說明 |
上面程式中的粗體字代碼使用dispatch_apply()函數控制提交的代碼塊執行5次,該函數所需的代碼塊略有不同,該代碼塊可以帶一個參數,該參數代表當前正在執行第幾次. |
只執行一次的任務
dispatch_once()函數將控制提交的代碼塊在整個應用的生命週期內最多隻執行一次----只有第1次提交該代碼塊時,
該代碼塊才會獲得執行的機會.而且dispatch_once()函數無須傳入隊列,這意味著系統將直接用主線程執行該函數提交的代碼塊.
dispatch_once()函數執行時需要傳入一個dispatch_once_t類型(本質就是long型整數)的指標(即predicate參數),
該指標變數用於判斷該代碼塊是否已經執行過.
下面樣本程式在介面上包含了一個按鈕,當使用者單擊該按鈕時激發的事件處理方法將會使用dispatch_once()函數提交代碼塊,
將代碼塊提交給主線程執行,因此該代碼塊可能阻塞主線程.但當使用者再次單擊該按鈕時,dispatch_once()函數提交的代碼塊不會再執行.
程式碼片段 |
1 ViewController.m 2 3 @implementation ViewController 4 5 - (void)viewDidLoad 6 7 { 8 9 [super viewDidLoad];10 11 }12 13 - (IBAction)clicked:(id)sender14 15 {16 17 static dispatch_once_t onceToken;18 19 dispatch_once(&onceToken, ^{20 21 NSLog(@”==執行代碼塊==”);22 23 // 線程暫停3秒24 25 [NSThread sleepForTimeInterval:3];26 27 });28 29 }30 31 @end |
|
|