iOS-GCD從認識到高深莫測

技 術 文 章 / 超 人


GCD 介紹
Grand Central Dispatch(GCD) 是Apple開發(fā)的一個多核編程的較新的解決方法,可譯為“牛逼的中樞調度器”純C語言,提供了非常多強大的函數(shù)。它主要用于優(yōu)化應用程序以支持多核處理器以及其他對稱處理系統(tǒng),GCD會自動利用更多的CPU內核(比如雙核、四核)。它是一個在線程池模式的基礎上執(zhí)行的并行任務。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用,iOS7時能開啟5、6條子線程, iOS8以后可以50、60條子線程。

GCD的優(yōu)勢

  • GCD會自動管理線程的生命周期(創(chuàng)建線程、調度任務、銷毀線程)所以只需要程序員決定創(chuàng)建什么類型的GCD隊列和執(zhí)行什么任務即可,其他由系統(tǒng)自行管理。
  • 任務的取出遵循FIFO模式:先進先出,后進后出
  • GCD定時器不受RunLoop約束,比NSTimer更加準時
  • 可操作可控性強,幾乎沒有GCD做不了的事情。

GCD的組成
隊列(queue)+ 任務(block里執(zhí)行的操作)

隊列的組成
是否開啟線程(同步,異步) + 任務執(zhí)行的方式(主隊列,串行隊列,并發(fā)隊列,全局并行隊列)

  • 同步(dispatch_sync):只能在當前線程執(zhí)行任務,不具備開啟新線程能力。
  • 異步(dispatch_async):可以在新線程執(zhí)行任務,具備開啟新線程能力。
  • 串行隊列:按照任務加入隊列的順序,依次執(zhí)行,只有前一個任務執(zhí)行完畢,才會執(zhí)行下一個任務。(推薦結合 異步使用)
/*
參數(shù)1:隊列名稱,同一個名稱獲取到的隊列是同一個
隊列類型:
DISPATCH_QUEUE_SERIAL:串行
DISPATCH_QUEUE_CONCURRENT:并行
*/
dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
  • 并行隊列:會先把所有任務放入隊列中,當任務添加完畢后,同時執(zhí)行所有任務。無法確定那個任務先完成。(推薦結合 異步使用)
dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
  • 主隊列dispatch_get_main_queue:GCD自帶的特殊隊列,所有放在主隊列里執(zhí)行的任務,都會在主線程執(zhí)行
dispatch_queue_t queue= dispatch_get_main_queue();
  • 全局隊列 dispatch_get_global_queue:GCD自帶的全局并行隊列,不需要自己去創(chuàng)建隊列。直接使用dispatch_get_global_queue來獲取,都是同一個隊列,非主線程。不要與 barrier 柵欄方法搭配使用, barrier 只有與自定義的并行隊列一起使用,才能讓 barrier 達到我們所期望的柵欄功能。與 串行隊列或者 global 隊列 一起使用,barrier 的表現(xiàn)會和 dispatch_sync 方法一樣。
/*
(里面的參數(shù)第一個為優(yōu)先級,可以默認為固定寫法)
全局并發(fā)隊列的優(yōu)先級(不論是串行還是并行,創(chuàng)建出來的隊列默認都是默認優(yōu)先級。可以使用set_target_queue來改變優(yōu)先級)
#define DISPATCH_QUEUE_PRIORITY_HIGH 2  高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0  默認(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)  低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  后臺 
*/
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

組合效果

同步+串行 :由于同步沒有開啟新線程的能力,所以會在主線程執(zhí)行任務,由于串行,會按照任務加入順序執(zhí)行任務,上一個執(zhí)行完,下一個才執(zhí)行

/* 創(chuàng)建串行隊列 */
dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
/* 使用同步把任務加入到串行隊列中, 第二個參數(shù)不用管統(tǒng)一填寫0, 我也不知道為什么,哈哈 */
dispatch_sync(queue,0){
    //執(zhí)行的任務
    NSLog(@"1------%@",[NSThread currentThread]);
};

異步+串行 :由于異步,會開啟新線程,所以會在子線程執(zhí)行,由于串行只會開啟一個子線程。依次執(zhí)行

/* 創(chuàng)建串行隊列 */
dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
/* 使用異步把任務加入到串行隊列中*/
dispatch_async(queue,0){
    //執(zhí)行的任務
    NSLog(@"1------%@",[NSThread currentThread]);
};

同步+并行 :由于并行需要同時執(zhí)行所有任務,但又是同步,不會開啟新線程去同時執(zhí)行所有任務,會在主隊列執(zhí)行。所以同步+并行會按照串行執(zhí)行,會先把所有任務加入主隊列在主線程中一個一個任務執(zhí)行,但是任務順序無法確定。

dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
/* 使用同步把任務加入到并行隊列中*/
dispatch_sync(queue,0){
    //執(zhí)行的任務
    NSLog(@"1------%@",[NSThread currentThread]);
};

異步+并行:由于異步會開啟一個新的線程,由于并行會同時執(zhí)行所有任務,有多少任務就會開啟多少線程(也不全是,有時候主線程空閑時也會執(zhí)行)。

dispatch_queue_t queue= dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
/* 使用異步把任務加入到并行隊列中*/
dispatch_async(queue,0){
    //執(zhí)行的任務
    NSLog(@"1------%@",[NSThread currentThread]);
};

全局隊列+同步或+異步:全局隊列就是并行模式。所以就不說流程了。全局并發(fā)隊列在整個應用程序中本身是默認存在的,并且對應有高優(yōu)先級、默認優(yōu)先級、低優(yōu)先級和后臺優(yōu)先級一共四個并發(fā)隊列,我們只是選擇其中的一個直接拿來用。而Crearte函數(shù)是實打實的從頭開始去創(chuàng)建一個隊列。在使用柵欄函數(shù)的時候,蘋果官方明確規(guī)定柵欄函數(shù)只有在和使用create函數(shù)自己的創(chuàng)建的并發(fā)隊列一起使用的時候才有效(沒有給出具體原因

同步+主隊列:同步不會開啟線程所以會在主線程,主隊列只會在主線程執(zhí)行。
注意:同步任務有一個特性,只要一添加到隊列中就要馬上執(zhí)行,主隊列中永遠就只要一條線程——主線程,此時主線程在等待著主隊列調度同步任務,而主隊列發(fā)現(xiàn)主線程上還有任務未執(zhí)行完,就不會讓同步任務添加到主線程上,由此就造成了互相等待(主隊列在等待主線程執(zhí)行完已有的任務,而主線程又在等待主隊列調度同步任務!此時也就是所謂的死鎖了!)

dispatch_sync(dispatch_get_main_queue(),0){
    //執(zhí)行任務
};

異步+主隊列:異步具有開啟子線程能力,但主隊列中只有一個主線程,所以任務會在主線程中按照順序一個一個執(zhí)行。類似與同步+串行

dispatch_async(dispatch_get_main_queue(),0){
    //執(zhí)行任務
};

暫?;蛉∠?/strong>隊列:GCD中的隊列也是可以暫停和恢復的,直接把相應的隊列作為參數(shù)就傳遞就可以。使用 dispatch_resume(queue1);和dispatch_suspend(queue1);


柵欄函數(shù)
  • 作用:只有當柵欄函數(shù)執(zhí)行完畢后才能執(zhí)行后面的函數(shù)
  • 需求:使用柵欄函數(shù)規(guī)定線程執(zhí)行順序
  • 注意點:柵欄函數(shù)不能使用全局并發(fā)隊列
// 柵欄函數(shù)不能使用全局并發(fā)隊列
  /* 創(chuàng)建并發(fā)隊列 */
 dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
    // 2. 異步函數(shù)
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i< 100; i++) {
            NSLog(@"queue1 ----%zd----- %@",i,[NSThread currentThread]);
        }
        
    });
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i< 100; i++) {
            NSLog(@"queue2 ----%zd----- %@",i,[NSThread currentThread]);
        }
    });
    
    
    // 柵欄函數(shù),因為隊列是并發(fā)的,會等所有任務都添加到隊列了才會統(tǒng)一一起執(zhí)行,但是由于加入柵欄函數(shù),所以會先把上面2個異步任務并行執(zhí)行,柵欄函數(shù)前的任務執(zhí)行完后,執(zhí)行柵欄函數(shù),最后才會執(zhí)行加柵欄函數(shù)后面的異步任務。
    //注意:任務都是一起加入到隊列中的,并不是執(zhí)行完柵欄函數(shù)后才加入柵欄函數(shù)后的隊列
    dispatch_barrier_async(queue, ^{
        NSLog(@"柵欄----------");
    });
    
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i< 100; i++) {
            NSLog(@"queue3 ----%zd----- %@",i,[NSThread currentThread]);
        }
    });

隊列組(Dispatch Queue)

dispatch_group_t:隊列是用來管理隊列里任務的執(zhí)行方式,隊列組則是用來管理隊列執(zhí)行任務的

  • 創(chuàng)建隊列組
  • 創(chuàng)建隊列
  • 使用隊列組異步函數(shù)來封裝任務, 然后提交到隊列中
  • 把當前所有任務執(zhí)行的情況, 都納入到隊列組監(jiān)聽的范圍中
    實例代碼:
// 1. 創(chuàng)建隊列組
 dispatch_group_t group = dispatch_group_create();

 // 2. 創(chuàng)建并發(fā)隊列
 dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);

 // 3. 使用異步函數(shù)組添加任務
 dispatch_group_async(group, queue, ^{
     NSLog(@"1---%@", [NSThread currentThread]);
 });

 dispatch_group_async(group, queue, ^{
     NSLog(@"2---%@", [NSThread currentThread]);
 });

 // 4. 讓隊列組監(jiān)聽任務的完成
 dispatch_group_notify(group, queue, ^{
    //當隊列組group管理的queue中所有任務執(zhí)行完畢后,就會在該函數(shù)中收到回調。類似與柵欄,如果后面還有任務,則必須等上面任務完成后且dispatch_group_notify任務執(zhí)行完畢后才會將后面等任務加入隊列組中等隊列
     NSLog(@"執(zhí)行完畢");
 });

隊列組+函數(shù) :只能使用異步

  • 首先要使用隊列組創(chuàng)建函數(shù), 創(chuàng)建一個隊列組
  • 創(chuàng)建一個并發(fā)隊列
  • 開啟隊列組, 然后使用異步函數(shù)封裝每一個任務
  • 當任務執(zhí)行結束之后, 要將加入到隊列組的任務移除隊列組(類似于引用計數(shù)管理, 計數(shù)+1必須伴隨計數(shù)-1) + 隊列組通常與dispatch_group_wait連用, 等待隊列組中的任務X秒, 之后再繼續(xù)執(zhí)行后面的任務
      // 1. 獲取隊列組
      dispatch_group_t group = dispatch_group_create();
      // 2. 創(chuàng)建全局并行隊列
      dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
      // 3. 開啟隊列組,將下面放入queue隊列的異步任務放入隊列組中
      dispatch_group_enter(group);
      // 4. 使用異步函數(shù)封裝任務
      dispatch_async(queue, ^{
          NSLog(@"1---%@", [NSThread currentThread]);
          // 5. 確保任務結束之后,讓該任務離開隊列組
          dispatch_group_leave(group);
      });

      dispatch_group_enter(group);
      dispatch_async(queue, ^{
          NSLog(@"2---%@", [NSThread currentThread]);
          dispatch_group_leave(group);
      });

   // 攔截通知
  //    dispatch_group_notify(group, queue, ^{
  //        NSLog(@"任務結束");
  //    });

      // 同步執(zhí)行,等待隊列組time的時間后執(zhí)行后面的代碼,如果隊列組中的任務都完成了,就返回0,如果沒完成,就返回非0值
      dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 0.00000002 * NSEC_PER_SEC);

      // 等待tiemr秒的時間,不管隊列中任務是否完成,都繼續(xù)往下執(zhí)行,如果改時間內任務完成了就返回0,否則是非零值
      long n = dispatch_group_wait(group, timer);
      NSLog(@"%ld", n);

Dispatch Source (信號源)

GCD中除了主要的 Dispatch Queue 外,還有不太引人注目的 Dispatch Source .它是BSD系內核慣有功能kqueue的包裝。kqueue 是在 XNU 內核中發(fā)生各種事件時,在應用程序編程方執(zhí)行處理的技術。其 CPU 負荷非常小,盡量不占用資源。kqueue 可以說是應用程序處理 XNU 內核中發(fā)生的各種事件的方法中最優(yōu)秀的一種。

Dispatch Source 也使用在了 Core Foundation 框架的用于異步網絡的API CFSocket 中。因為Foundation 框架的異步網絡 API 是通過CFSocket實現(xiàn)的,所以可享受到僅使用 Foundation 框架的 Dispatch Source 帶來的好處。

那么優(yōu)勢何在?使用的 Dispatch Source 而不使用 dispatch_async 的唯一原因就是利用聯(lián)結的優(yōu)勢。

聯(lián)結的大致流程:在任一線程上調用它的的一個函數(shù) dispatch_source_merge_data 后,會執(zhí)行 Dispatch Source 事先定義好的句柄(可以把句柄簡單理解為一個 block )。

這個過程叫 Custom event。是 dispatch source 支持處理的一種事件。

簡單地說,一個監(jiān)視某些類型事件的對象。當這些事件發(fā)生時,它自動將定義好的block放入一個dispatch queue的執(zhí)行例程中。這種事件是由你調用 dispatch_source_merge_data 函數(shù)來向自己發(fā)出的信號。

dispatch_source_t add = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0,
                                                   dispatch_get_main_queue());

dispatch_source_create參數(shù)說明:
參數(shù):

參數(shù) 意義
type dispatch源可處理的事件
handle 可以理解為句柄、索引或id,假如要監(jiān)聽進程,需要傳入進程的ID
mask 可以理解為描述,提供更詳細的描述,讓它知道具體要監(jiān)聽什么
queue 自定義源需要的一個隊列,用來處理所有的響應句柄(block)

Dispatch Source可處理的所有事件(type)

名稱 內容
DISPATCH_SOURCE_TYPE_DATA_ADD 自定義的事件,變量增加
DISPATCH_SOURCE_TYPE_DATA_OR 自定義的事件,變量OR
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口發(fā)送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
DISPATCH_SOURCE_TYPE_PROC 進程監(jiān)聽,如進程的退出、創(chuàng)建一個或更多的子線程、進程收到UNIX信號
DISPATCH_SOURCE_TYPE_READ IO操作,如對文件的操作、socket操作的讀響應
DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信號時響應
DISPATCH_SOURCE_TYPE_TIMER 定時器
DISPATCH_SOURCE_TYPE_VNODE 文件狀態(tài)監(jiān)聽,文件被刪除、移動、重命名
DISPATCH_SOURCE_TYPE_WRITE IO操作,如對文件的操作、socket操作的寫響應

注意:

  • DISPATCH_SOURCE_TYPE_DATA_ADD
    當同一時間,一個事件的的觸發(fā)頻率很高,那么Dispatch Source會將這些響應以ADD的方式進行累積,然后等系統(tǒng)空閑時最終處理,如果觸發(fā)頻率比較零散,那么Dispatch Source會將這些事件分別響應。
  • DISPATCH_SOURCE_TYPE_DATA_OR 和上面的一樣,是自定義的事件,但是它是以OR的方式進行累積

信號源使用函數(shù)

dispatch_suspend(queue) //掛起隊列

dispatch_resume(source) //分派源創(chuàng)建時默認處于暫停狀態(tài),在分派源分派處理程序之前必須先恢復

dispatch_source_merge_data //向分派源發(fā)送事件,需要注意的是,不可以傳遞0值(事件不會被觸發(fā)),同樣也不可以傳遞負數(shù)。

dispatch_source_set_event_handler //設置響應分派源事件的block,在分派源指定的隊列上運行

dispatch_source_get_data //得到分派源的數(shù)據

uintptr_t dispatch_source_get_handle(dispatch_source_t source); //得到dispatch源創(chuàng)建,即調用dispatch_source_create的第二個參數(shù)

unsigned long dispatch_source_get_mask(dispatch_source_t source); //得到dispatch源創(chuàng)建,即調用dispatch_source_create的第三個參數(shù)

void dispatch_source_cancel(dispatch_source_t source); //取消dispatch源的事件處理--即不再調用block。如果調用dispatch_suspend只是暫停dispatch源。

long dispatch_source_testcancel(dispatch_source_t source); //檢測是否dispatch源被取消,如果返回非0值則表明dispatch源已經被取消

void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler); //dispatch源取消時調用的block,一般用于關閉文件或socket等,釋放相關資源

void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler); //可用于設置dispatch源啟動時調用block,調用完成后即釋放這個block。也可在dispatch源運行當中隨時調用這個函數(shù)。

GCD延遲操作

/*
DISPATCH_TIME_NOW:現(xiàn)在開始的意
2.0 * NSEC_PER_SEC:設置的秒數(shù)
dispatch_get_main_queue():主隊列的意思
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    2秒后執(zhí)行這里的代碼... 在哪個線程執(zhí)行,跟隊列類型有關  
});

一次性代碼
使用dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次,如果把onceToken設置成全局變量,可以控制onceToken的值來控制代碼再次執(zhí)行

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
     //程序運行過程中,永遠只執(zhí)行1次的代碼(這里面默認是線程安全的)
});

重復多次執(zhí)行相同任務

/* 獲取全局隊列 */
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
    //使用dispatch_apply()函數(shù)控制提交的任務代碼塊執(zhí)行5次,該函數(shù)所需的代碼塊可以帶一個參數(shù),這個參數(shù)表示當前正在執(zhí)行第幾次  
    dispatch_apply(5,queue,^(size_t time){  
        NSLog(@"---執(zhí)行%lu次---%@",time,[NSThread currentThread]);  
    });

GCD定時器

0.創(chuàng)建全局隊列,由于定時任務,每次都只有一個任務,所以串行并行無所謂
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 
1.創(chuàng)建一個GCD的定時器
 
 第一個參數(shù):說明這是一個定時器
 第四個參數(shù):GCD的回調任務添加到那個隊列中執(zhí)行,如果是主隊列則在主線程執(zhí)行
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
2.設置定時器的開始時間,間隔時間以及精準度
 
設置開始時間,三秒鐘之后調用
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW,3.0 *NSEC_PER_SEC);
設置定時器工作的間隔時間
uint64_t intevel = 1.0 * NSEC_PER_SEC;
 
 第一個參數(shù):要給哪個定時器設置
 第二個參數(shù):定時器的開始時間DISPATCH_TIME_NOW表示從當前開始
 第三個參數(shù):定時器調用方法的間隔時間
 第四個參數(shù):定時器的精準度,如果傳0則表示采用最精準的方式計算,如果傳大于0的數(shù)值,則表示該定時切換i可以接收該值范圍內的誤差,通常傳0
 該參數(shù)的意義:可以適當?shù)奶岣叱绦虻男阅? 注意點:GCD定時器中的時間以納秒為單位(面試)
 
 
dispatch_source_set_timer(timer, start, intevel, 0 * NSEC_PER_SEC);
 
3.設置定時器開啟后回調的方法
 
 第一個參數(shù):要給哪個定時器設置
 第二個參數(shù):回調block
 
dispatch_source_set_event_handler(timer, ^{
    NSLog(@"------%@",[NSThread currentThread]);
});
 
4.執(zhí)行定時器
dispatch_resume(timer);
 
注意:dispatch_source_t本質上是OC類,在這里是個局部變量,需要強引用
self.timer = timer;

進度條

//1、指定DISPATCH_SOURCE_TYPE_DATA_ADD,做成Dispatch Source(分派源)。設定Main Dispatch Queue 為追加處理的Dispatch Queue
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());

    __block NSUInteger totalComplete = 0;

    dispatch_source_set_event_handler(source, ^{

        //當處理事件被最終執(zhí)行時,計算后的數(shù)據可以通過dispatch_source_get_data來獲取。這個數(shù)據的值在每次響應事件執(zhí)行后會被重置,所以totalComplete的值是最終累積的值。
        NSUInteger value = dispatch_source_get_data(source);

        totalComplete += value;

        NSLog(@"進度:%@", @((CGFloat)totalComplete/100));

        NSLog(@":large_blue_circle:線程號:%@", [NSThread currentThread]);
    });

    //分派源創(chuàng)建時默認處于暫停狀態(tài),在分派源分派處理程序之前必須先恢復。
    dispatch_resume(source);

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //2、恢復源后,就可以通過dispatch_source_merge_data向Dispatch Source(分派源)發(fā)送事件:
    for (NSUInteger index = 0; index < 100; index++) {

        dispatch_async(queue, ^{

            dispatch_source_merge_data(source, 1);

            NSLog(@":recycle:線程號:%@~~~~~~~~~~~~i = %ld", [NSThread currentThread], index);

            usleep(20000);//0.02秒

        });
    }

    //3、比較上面的for循環(huán)代碼,將dispatch_async放在外面for循環(huán)的外面,打印結果不一樣
    //dispatch_async(queue, ^{
    //
    //    for (NSUInteger index = 0; index < 100; index++) {
    //
    //        dispatch_source_merge_data(source, 1);
    //
    //        NSLog(@":recycle:線程號:%@~~~~~~~~~~~~i = %ld", [NSThread currentThread], index);
    //
    //        usleep(20000);//0.02秒
    //    }
    //});


    //2是將100個任務添加到queue里面,而3是在queue里面添加一個任務,而這一個任務做了100次循環(huán)

等待多個網絡請求全部完成后執(zhí)行其他操作
有時候我們會遇到,請求多個無互相關聯(lián)的網絡請求。如果先等第一個網絡請求成功后在執(zhí)行第二個網絡請求,請求事件就是累加等。 而使用信號量可以達到多個網絡請求同時發(fā)出分別接受,這樣多個網絡請求所消耗等時間就是請求中耗時最長那一個的時間,而不是累加時間。

/* 創(chuàng)建信號量 */
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
/* 由于有的時候代碼實在主線中執(zhí)行的,不能用信號量堵塞主線程*/
dispatch_async(dispatch_get_global_queue(0, 0), ^{



/* 網絡請求1 */
[[AFHTTPSessionManager manager] GET:URL parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {  }     
 success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {  
/* 請求成功,發(fā)送一個信號量*/
dispatch_semaphore_signal(semaphore);
}
failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull   error) {  
/* 請求失敗也要發(fā)送一個信號量,不然一會值堵塞線程*/
dispatch_semaphore_signal(semaphore);
}];



/* 網絡請求2 */
[[AFHTTPSessionManager manager] GET:URL parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {  }     
 success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {  
/* 請求成功,發(fā)送一個信號量*/
dispatch_semaphore_signal(semaphore);
}
failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull   error) {  
/* 請求失敗也要發(fā)送一個信號量,不然一會值堵塞線程*/
dispatch_semaphore_signal(semaphore);
}];



/* 網絡請求3 */
[[AFHTTPSessionManager manager] GET:URL parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {  }     
 success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {  
/* 請求成功,發(fā)送一個信號量*/
dispatch_semaphore_signal(semaphore);
}
failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull   error) {  
/* 請求失敗也要發(fā)送一個信號量,不然一會值堵塞線程*/
dispatch_semaphore_signal(semaphore);
}];
/*等待信號,因為有3個網絡請求,所以要等待3個信號,這里會堵塞線程,等到3個任務都完成了,才會執(zhí)行后面都代碼*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
/* 在主線程中刷新界面*/
dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"所有網絡請求完成了,可以刷新界面了");
        });
});

(未完待續(xù)...)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容