iOS 關于dispatch_semaphore_t、dispatch_source_t 和 dispatch_group_t 的簡單實用,用于多網絡異步回調通知

問題來源: 最近遇到了一個多網絡異步回調的問題,其實也就是我們請求的數據是異步的,我們使用了帶有返回值的方法,結果我們先獲取的結果都是空的,這個其實對新手來說,可能不知道為什么會有這個結果,這個其實稍微百度一下就能找到答案,不過還是寫一下,為大家處理一下盲區


我們主要介紹3中方法,來獲取異步方法中的回調結果


一、 使用信號量 dispatch_semaphore_t 控制請求

  • 我們先看一下實際應用中的一些列子,我們盡量仿真模擬真實的網絡請求我們看下代碼如何工作的:
/*!
 *  @author Raybon.Lee, 16-04-07 10:04:02
 *
 *  @brief 從服務器請求數據,異步返回數據,并更新UI,我們這里盡量高仿真實的網絡請求環境
 *
 *  @return 返回一個數組
 *
 *  @since <#1.0#>
 */

- (__kindof NSArray  *)fetchDataFromServe{

        //假如下面這個數組是用來存放數據的
    NSMutableArray * array = [NSMutableArray arrayWithCapacity:0];
        //下面這個來代替我們平時常用的異步網絡請求
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i=0; i<10; i++) {
            [array addObject:[NSNumber numberWithInt:i]];
        }
        NSLog(@"array = %@",array);

    });

    return array;
}

正常情況下,我們可能會有這種寫法,因為沒注意到返回數據是異步的
我們看下調用返回:

 NSArray  * resultArray = [self fetchDataFromServe];
 NSLog(@"resultArrsy = %@",resultArray);

*控制臺輸出結果:

2016-04-07 10:30:45.868 ABNumDemo[4129:1394388] resultArrsy = (
)
2016-04-07 10:30:45.875 ABNumDemo[4129:1394441] array = (
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9
)

我們第一眼看到的感覺一般都是很明確,這個值肯定存在,那為什么最終獲取的值就是不對呢,有時候考慮問題很容易凌亂,上面的結果一定反饋給我們信息了,結果是空的

  • 現在我們修改一下方法,使用信號量控制,順便說一下信號量是如何工作
- (__kindof NSArray  *)fetchDataFromServe{

        //修改下面的代碼,使用信號量來進行一個同步數據
        //我們傳入一個參數0 ,表示沒有資源,非0 表示是有資源,這一點需要搞清楚  
//補充:這里的整形參數如果是非0 就是總資源
    dispatch_semaphore_t semaoh = dispatch_semaphore_create(0);

        //假如下面這個數組是用來存放數據的
    NSMutableArray * array = [NSMutableArray arrayWithCapacity:0];
        //下面這個來代替我們平時常用的異步網絡請求
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i=0; i<10; i++) {
            [array addObject:[NSNumber numberWithInt:i]];

        }
        NSLog(@"array = %@",array);
            //謝謝葉神的糾正 修正:發送信號,信號量  管理資源數+1  車輛如果遇到綠色信號燈,等待的車輛就會減少,也就是資源數減少,一直減少到 dispatch_semaphore_wait  這個函數返回 0 才會繼續執行,
//之前注釋有點問題,這個通過相當于 放行操作 執行信號數量+1,這個地方一定觸發信號,就會通知wait 函數進行 -1 操作,也就是后面可以繼續通行

        dispatch_semaphore_signal(semaoh);

    });
        //信號等待 時,資源數 -1  阻塞當前線程 這個難理解的可以理解成等待紅綠燈的車輛,紅燈等待車輛
        //車輛自然是累加的排隊等候,沒有資源,會一直觸發信號控制
    dispatch_semaphore_wait(semaoh, DISPATCH_TIME_FOREVER);
    

    return array;
}

  • 信號量的理解:
    我們初始化的時候會先設置一個信號總量,如果信號總量的整形參數是0 ,那么就是沒有資源需要等待,我們如果下面執行wait 操作,那么相當于線程擁堵,執行信號-1 操作,如果現在是綠燈通行狀態,我們會設置 sigle 信號,執行一個信號+1 操作,告訴當前線程,有一個信號可以釋放了,大概可以這么來理解
    修改之后我們再來看一下控制臺的輸出:
2016-04-07 10:36:17.361 ABNumDemo[4136:1395451] array = (
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9
)
2016-04-07 10:36:17.364 ABNumDemo[4136:1395425] resultArrsy = (
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9
)

是不是看到我們想要的效果了,其實我們就是要異步回調有結果之后再釋放資源數

二、 下面我們再看下 dispatch_group_t 的使用方法

  • 同樣我們還是寫一個仿真請求網絡的地方
- (NSArray *)fetchTheNetUserData{

    dispatch_group_t group = dispatch_group_create();
    NSMutableArray * array = [NSMutableArray array];
    for (int i=0; i<5; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [array addObject:[NSNumber numberWithInt:i]];
        });


    }
    return array;
}

我們現在調用一下函數看下輸出:

 NSArray * groupArray = [self fetchTheNetUserData];
    NSLog(@"grouparray = %@",groupArray);


2016-04-07 11:17:53.508 ABNumDemo[4143:1400362] grouparray = (
)

這個結果其實不是想要的結果,我們稍微修改一下

- (NSArray *)fetchTheNetUserData{

        //使用GCD創建一個group 組,這個其實不是很好理解,我們可以理解成,創建一個組,然后給這個組添加任務,等到所有組的任務都結束之后在進行一個頒獎典禮,這個就是我們要的效果
    dispatch_group_t group = dispatch_group_create();
    NSMutableArray * array = [NSMutableArray array];
    for (int i=0; i<5; i++) {
            //group  進入一個組  和dispatch_group_leave  這兩個必須是成對出現,缺一不可,如果對應不上則會出現線程阻塞,
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [array addObject:[NSNumber numberWithInt:i]];
                //dispatch_group_leave  離開一個組 ,這個和排毒其實有點類似
            dispatch_group_leave(group);
        });
    }
        //dispatch_group_wait  這個函數返回 0則會繼續執行,否則一直等待 group 組內的所有成員任務完畢,這個其實也是資源數增加的一個函數,等待當前線程結束
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"array = %@",array);

    return array;
}

  • 增加 ,一下兩種形式等價,只不過使用方式不同,看自己需求
這兩個還是稍微有點差別,一個是設置資源線程等待
  dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
下面這個是通知形式的,可以做一些沒有返回值方法的更新UI操作
 dispatch_group_notify(group, dispatch_get_main_queue(), ^{
  //[tableView reloadData];
    });

修改之后的輸出環境:

2016-04-07 11:22:25.251 ABNumDemo[4149:1401314] array = (
    0,
    1,
    2,
    3,
    4
)
2016-04-07 11:22:25.258 ABNumDemo[4149:1401314] grouparray = (
    0,
    1,
    2,
    3,
    4
)

通過以上兩種方法,或許我們已經看到想要的結果。

  • 有個需要注意的地方,在我們使用以上兩者中的時候,如果使用了AF嵌套,AF缺省的時候是主線程,而我們的wait 函數也是主線程,此時會造成死鎖,平時使用的時候要注意這一點。這兩天在調試的時候發現了這個,表明一下

三、 使用block 獲取異步回調的結果

  • 我們還是看代碼,直觀明了:
- (void)queryTheDataRequestWithCompletion:(void (^)(NSString * string))block{

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        block(@"test-block");

    });
}
- (void)requestDataFormServe:(CallBlock)block{

    block(@{@"name":@"raybon"});
}

調用這兩個方法:

 [self requestDataFormServe:^(NSDictionary *dict) {
        NSString * name = dict[@"name"];
        NSLog(@"name = %@",name);

    }];
 [self queryTheDataRequestWithCompletion:^(NSString *string) {
        NSLog(@"string = ^%@",string);
    }];

接著我們看下輸出控制臺的結果:

2016-04-07 11:33:25.153 ABNumDemo[4155:1402837] name = raybon
2016-04-07 11:33:25.160 ABNumDemo[4155:1402877] string = ^test-block

這種采用block的方法,我覺得是不是更好理解呢,還好用,但是這個要針對不同的情況采取不同的方法,并不是固定的,這里只是提供幾種思路而已,有更好的想法都可以留言增加上去,技術么,當然是越優化越好。

補充一、dispatch_semaphore_t 控制請求
  • 增加一個通過源控制的請求,也是一個線程安全的,這個用的還是GCD的API,我們用代碼分析
```
      //創建一個源   DISPATCH_SOURCE_TYPE_DATA_ADD  源類型是增加數據
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_global_queue(0, 0));
    //設置事件回調
dispatch_source_set_event_handler(source, ^{

        //數據執行完畢,通知我更新UI操作
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"## 我已經收到數據,開始更新UI操作 ##");
        dispatch_source_cancel(source);
    });

});
    //source 默認處于suspend 暫停 狀態 ,我們要執行 dispatch_resume  繼續執行
dispatch_resume(source);
dispatch_async(dispatch_get_main_queue(), ^{

        //此處是網絡請求,請求到數據之后,我們更新一個源
    NSLog(@"## 通知數據合并更新 ##");
    dispatch_source_merge_data(source, 1);
});
unsigned long num =   dispatch_source_get_data(source);
NSLog(@"data = %ld",num);
    //得到dispatch  源 ,獲取的是dispatch_source_create  的第三個參數,
unsigned long num1 =    dispatch_source_get_mask(source);
    //獲取dispatch 源  獲取的是dispatch_create 的第二個參數位置的信息
unsigned long num2 = dispatch_source_get_handle(source);
    //  源取消時的回調,用于關閉流處理
dispatch_source_set_cancel_handler(source, ^{
    NSLog(@"## 如果source 被取消,則執行這個函數 ##");
});
    //檢測源是否取消,如果是非0 則表示取消
long cancelNum =   dispatch_source_testcancel(source);
    //可用于源啟動時調用block,調用完成后釋放這個block,也可以在源執行中調用這個block
dispatch_source_set_registration_handler(source, ^{
    NSLog(@"我是注冊收到的回調");
});

```
* 這里我們看一下API的作用
   該API主要是監聽數據更新完畢的block回調,最簡單的方法就是網絡請求完畢需要更新UI的時候,我們會在這里面執行block
    ```      
void

dispatch_source_set_event_handler(dispatch_source_t source,
dispatch_block_t handler);
* 合并數據源的API
dispatch_source_merge_data(source, 1);
```
該API主要提供數據更新完畢時候調用一次,第二個參數不能設置為0, 否則block無反應
* 其他API就是取消源的API,我們不用的時候可以取消掉。

總結:

以上幾種方法適合我們在做一些簡單的遍歷操作的時候挺適合的,針對請求的一些異步數據,還是提醒一點,使用這個一定要注意是否有嵌套主線程,造成線程阻塞,只要思路清晰,處理這些問題還是沒問題的

  • 最后我們擴充一個資源共享的問題,同樣我們還是使用信號量控制

這個是線程安全的,每次只允許一個線程進行讀寫操作,遇到讀寫問題可以參考這里哦

//這里我們指定一個資源 wait 返回值就不會為0 執行發出信號操作
    dispatch_semaphore_t semat = dispatch_semaphore_create(1);
    NSMutableArray * array = [NSMutableArray array];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        for (int i=0; i<1000; i++) {
             dispatch_semaphore_wait(semat, DISPATCH_TIME_FOREVER);
            [array addObject:[NSNumber numberWithInt:i]];
             dispatch_semaphore_signal(semat);

        }

    });

  • 補充:
    使用信號量控制AF請求,會遇到阻塞,我們這個時候除了使用group 信號控制,這里推薦葉神提供的 runloopObserve ,簡單理解就是通過runloop 的進入,到runloop 的結束,釋放信號,這個暫且未研究完畢,完畢研究會繼續追加此文。也希望有使用到這個的大神,直接留言,會在此文追加。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Dispatch Sources 現代系統通常提供異步接口,允許應用向系統提交請求,然后在系統處理請求時應用可以繼...
    好雨知時節浩宇閱讀 3,848評論 2 5
  • 支持原創 現代系統通常提供異步接口,允許應用向系統提交請求,然后在系統處理請求時應用可以繼續處理自己的事情。Gra...
    John_LS閱讀 3,612評論 3 2
  • Dispatch Sources 現代系統通常提供異步接口,允許應用向系統提交請求,然后在系統處理請求時應用可以繼...
    YangPu閱讀 340評論 0 0
  • 程序中同步和異步是什么意思?有什么區別? 解釋一:異步調用是通過使用單獨的線程執行的。原始線程啟動異步調用,異步調...
    風繼續吹0閱讀 1,050評論 1 2
  • 給大地來點湯料 用 二月衍接 時不時的不顧人情 用猛烈 砸濕 檐下的燕窩 只有,地皮下的草芽 最為饑渴 被他...
    一池凹水凸龍閱讀 236評論 0 4