多個網路請求成功返回再執行另外任務的思路分析(iOS)

来源:http://www.cnblogs.com/SUPER-F/archive/2017/08/15/7365699.html
-Advertisement-
Play Games

前言 今天我們來討論一個經常出現的需求場景,也是一個老話題。在開發中我們往往會遇到需要進行多個網路請求,並且需要多個網路請求成功返回後再做其他事的場景。比如同一個界面顯示的內容需要用到兩個網路介面,而需求又希望成功返回兩個介面的數據再進行頁面展示;又比如喜歡挖坑的後臺同學就只提供了返回一條數據的介面 ...


前言

今天我們來討論一個經常出現的需求場景,也是一個老話題。在開發中我們往往會遇到需要進行多個網路請求,並且需要多個網路請求成功返回後再做其他事的場景。比如同一個界面顯示的內容需要用到兩個網路介面,而需求又希望成功返回兩個介面的數據再進行頁面展示;又比如喜歡挖坑的後臺同學就只提供了返回一條數據的介面,但需求卻希望我們在一個界面同時顯示幾條數據的情況。

正題

我們不討論什麼執行完一個請求再執行一個這種串列的低效率方法,以下分析都是在非同步的基礎上進行的。
廢話少說,直奔正題!先上個網路請求的模擬代碼。

 1 //模擬一個網路請求方法 get/post/put...etc
 2 - (void)httpRequest:(NSString *)method param:(NSDictionary *)param completion:(void(^)(id response))block{
 3     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 4         NSString *commend = [param objectForKey:commandKey];
 5         NSLog(@"request:%@ run in thread:%@", commend, [NSThread currentThread]);
 6         NSTimeInterval sleepInterval = arc4random() % 10;
 7         [NSThread sleepForTimeInterval:sleepInterval];
 8         dispatch_async(dispatch_get_main_queue(), ^{
 9             NSLog(@"requset:%@ done!", commend);
10             block(nil);
11         });
12     });
13 }

 

不可行的直接使用group的方案

對於這樣的需求,我們自然而然就想到了使用GCD group,先上代碼

 1 - (void)testUsingGroup1{
 2     NSArray *commandArray = @[@"requestcommand1", @"requestcommand2", @"requestcommand3", @"requestcommand4", @"requestcommand5"];
 3     dispatch_group_t group = dispatch_group_create();
 4     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 5 
 6     [commandArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
 7         dispatch_group_async(group, queue, ^{
 8             NSLog(@"%@ in group thread:%@", obj, [NSThread currentThread]);
 9             [self httpRequest:nil param:@{commandKey : obj} completion:^(id response) {
10 
11             }];
12         });
13     }];
14     dispatch_group_notify(group, dispatch_get_main_queue(), ^{
15         NSLog(@"all http request done!");
16         NSLog(@"UI update in main thread!");
17     });
18 }

 

代碼很快寫完了,但卻存在問題,我們來看一下運行結果:

 1 2017-04-29 21:49:27.336 TestMutiRequest[1345:82575] requestcommand2 in group thread:<NSThread: 0x600000262a40>{number = 4, name = (null)}
 2 2017-04-29 21:49:27.336 TestMutiRequest[1345:82576] requestcommand1 in group thread:<NSThread: 0x60000007f2c0>{number = 3, name = (null)}
 3 2017-04-29 21:49:27.336 TestMutiRequest[1345:82578] requestcommand3 in group thread:<NSThread: 0x608000263c00>{number = 5, name = (null)}
 4 2017-04-29 21:49:27.336 TestMutiRequest[1345:82638] requestcommand4 in group thread:<NSThread: 0x600000262b00>{number = 6, name = (null)}
 5 2017-04-29 21:49:27.336 TestMutiRequest[1345:82575] requestcommand5 in group thread:<NSThread: 0x600000262a40>{number = 4, name = (null)}
 6 2017-04-29 21:49:27.336 TestMutiRequest[1345:82576] request:requestcommand2 run in thread:<NSThread: 0x60000007f2c0>{number = 3, name = (null)}
 7 2017-04-29 21:49:27.337 TestMutiRequest[1345:82639] request:requestcommand1 run in thread:<NSThread: 0x608000264000>{number = 7, name = (null)}
 8 2017-04-29 21:49:27.337 TestMutiRequest[1345:82578] request:requestcommand3 run in thread:<NSThread: 0x608000263c00>{number = 5, name = (null)}
 9 2017-04-29 21:49:27.337 TestMutiRequest[1345:82638] request:requestcommand4 run in thread:<NSThread: 0x600000262b00>{number = 6, name = (null)}
10 2017-04-29 21:49:27.338 TestMutiRequest[1345:82575] request:requestcommand5 run in thread:<NSThread: 0x600000262a40>{number = 4, name = (null)}
11 2017-04-29 21:49:27.391 TestMutiRequest[1345:82507] all http request done!
12 2017-04-29 21:49:27.429 TestMutiRequest[1345:82507] UI update in main thread!
13 2017-04-29 21:49:27.432 TestMutiRequest[1345:82507] requset:requestcommand2 done!
14 2017-04-29 21:49:27.435 TestMutiRequest[1345:82507] requset:requestcommand3 done!
15 2017-04-29 21:49:27.437 TestMutiRequest[1345:82507] requset:requestcommand4 done!
16 2017-04-29 21:49:28.347 TestMutiRequest[1345:82507] requset:requestcommand5 done!
17 2017-04-29 21:49:35.399 TestMutiRequest[1345:82507] requset:requestcommand1 done!

 

註意這裡:

1 2017-04-29 21:49:27.338 TestMutiRequest[1345:82575] request:requestcommand5 run in thread:<NSThread: 0x600000262a40>{number = 4, name = (null)}
2 2017-04-29 21:49:27.391 TestMutiRequest[1345:82507] all http request done!
3 2017-04-29 21:49:27.429 TestMutiRequest[1345:82507] UI update in main thread!
4 2017-04-29 21:49:27.432 TestMutiRequest[1345:82507] requset:requestcommand2 done!

 

結果很明顯,並不能得出我們需要的結果!!
問題究竟出現哪呢?!!
讓我們在回顧一下group的概念,group的設計就是為了方便我們執行完一系列的任務之後再執行其他的任務,但是不能忽視的是,這裡的任務是有要求的,這裡的任務必須要是同步執行的!!如果任務是非同步的,group只會執行完任務裡面非同步之前的代碼以及分發非同步任務就返回了!!也就代表分發group的當前這個任務完成了!但事實卻是這個任務的一部分子任務在其他線程執行了,而且不一定已執行結束返回。
問題分析到這裡,理所當然的就會出現以上結果的問題。
解決的方案也很自然想法了,就是想辦法使分發到group的任務是同步執行的。
順便提一點,雖然任務未能順利完成,但我們可以註意到GCD實現的一些細節,在這裡group到另外非同步方法的執行,GCD並沒有重新創建新的線程,而是重用了group已創建的線程。

改進的group方案

這裡我們使用dispatch_semaphore_t使單個請求任務同步執行。

 1 - (void)httpRequest2:(NSString *)method param:(NSDictionary *)param completion:(void(^)(id response))block{
 2     dispatch_semaphore_t sem = dispatch_semaphore_create(0);
 3     [self httpRequest:method param:param completion:^(id response) {
 4         if (block) {
 5             block(response);
 6         }
 7         dispatch_semaphore_signal(sem);
 8     }];
 9     dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
10 }

 

testUsingGroup方法也相應改成對httpRequest2方法的調用

 1 - (void)testUsingGroup2{
 2     NSArray *commandArray = @[@"requestcommand1", @"requestcommand2", @"requestcommand3", @"requestcommand4", @"requestcommand5"];
 3     dispatch_group_t group = dispatch_group_create();
 4     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 5 
 6     [commandArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
 7         dispatch_group_async(group, queue, ^{
 8             NSLog(@"%@ in group thread:%@", obj, [NSThread currentThread]);
 9             [self httpRequest2:nil param:@{commandKey : obj} completion:^(id response) {
10 
11             }];
12         });
13     }];
14     dispatch_group_notify(group, dispatch_get_main_queue(), ^{
15         NSLog(@"all http request done!");
16         NSLog(@"UI update in main thread!");
17     });  
18 }

 

我們再來看一下結果:

 1 2017-04-29 22:02:01.744 TestMutiRequest[1381:90462] requestcommand4 in group thread:<NSThread: 0x60000007bfc0>{number = 6, name = (null)}
 2 2017-04-29 22:02:01.744 TestMutiRequest[1381:90404] requestcommand2 in group thread:<NSThread: 0x608000073880>{number = 4, name = (null)}
 3 2017-04-29 22:02:01.744 TestMutiRequest[1381:90406] requestcommand1 in group thread:<NSThread: 0x60000007ba80>{number = 3, name = (null)}
 4 2017-04-29 22:02:01.744 TestMutiRequest[1381:90403] requestcommand3 in group thread:<NSThread: 0x60000007bec0>{number = 5, name = (null)}
 5 2017-04-29 22:02:01.745 TestMutiRequest[1381:90463] requestcommand5 in group thread:<NSThread: 0x60000007be80>{number = 7, name = (null)}
 6 2017-04-29 22:02:01.745 TestMutiRequest[1381:90464] request:requestcommand4 run in thread:<NSThread: 0x60000007c440>{number = 8, name = (null)}
 7 2017-04-29 22:02:01.746 TestMutiRequest[1381:90465] request:requestcommand2 run in thread:<NSThread: 0x60000007c4c0>{number = 9, name = (null)}
 8 2017-04-29 22:02:01.746 TestMutiRequest[1381:90466] request:requestcommand1 run in thread:<NSThread: 0x60000007c540>{number = 10, name = (null)}
 9 2017-04-29 22:02:01.746 TestMutiRequest[1381:90464] request:requestcommand3 run in thread:<NSThread: 0x60000007c440>{number = 8, name = (null)}
10 2017-04-29 22:02:01.746 TestMutiRequest[1381:90467] request:requestcommand5 run in thread:<NSThread: 0x608000073ec0>{number = 11, name = (null)}
11 2017-04-29 22:02:01.751 TestMutiRequest[1381:90356] requset:requestcommand4 done!
12 2017-04-29 22:02:01.821 TestMutiRequest[1381:90356] requset:requestcommand3 done!
13 2017-04-29 22:02:02.817 TestMutiRequest[1381:90356] requset:requestcommand1 done!
14 2017-04-29 22:02:03.796 TestMutiRequest[1381:90356] requset:requestcommand5 done!
15 2017-04-29 22:02:07.817 TestMutiRequest[1381:90356] requset:requestcommand2 done!
16 2017-04-29 22:02:07.818 TestMutiRequest[1381:90356] all http request done!
17 2017-04-29 22:02:07.818 TestMutiRequest[1381:90356] UI update in main thread!

 

這個結果就是我們預期希望得到的!
但是不能高興的太早,這個方法需要實現了我們的需求,但是確實存在問題的。我們可以看一下當前的線程情況。整整比上一種不可行方案多出了一倍的線程數(5條線程->10條線程)!!
這是怎麼發生的呢?剛好是因為我們把請求方法改成了同步的,但是網路請求是在其他線程執行的!系統分配給執行httpRequest2的線程必須等待進行具體網路請求的線程執行結束後再能繼續執行,否則繼續等待,相對上一種方案來說,這個線程就是剛好多出來的線程,這在上一種方案是不存在的。也就是剛好多了一倍。雖然有代價,但是我們的任務也算順利完成了。

不額外增加多一倍線程的方法(dispatch_semaphore_t)

使用信號量

 1 - (void)testUsingSemaphore{
 2     dispatch_semaphore_t sem = dispatch_semaphore_create(0);
 3 
 4     NSArray *commandArray = @[@"requestcommand1", @"requestcommand2", @"requestcommand3", @"requestcommand4", @"requestcommand5"];
 5 
 6     NSInteger commandCount = [commandArray count];
 7     //代表http訪問返回的數量
 8     //這裡模仿的http請求block塊都是在同一線程(主線程)執行返回的,所以對這個變數的訪問不存在資源競爭問題,故不需要枷鎖處理
 9     //如果網路請求在不同線程返回,要對這個變數進行枷鎖處理,不然很會有資源競爭危險
10     __block NSInteger httpFinishCount = 0;
11     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
12         //demo testUsingSemaphore方法是在主線程調用的,不直接調用遍歷執行,而是嵌套了一個非同步,是為了避免主線程阻塞
13         NSLog(@"start all http dispatch in thread: %@", [NSThread currentThread]);
14         [commandArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
15             [self httpRequest:nil param:@{commandKey : obj} completion:^(id response) {
16                 //全部請求返回才觸發signal
17                 if (++httpFinishCount == commandCount) {
18                     dispatch_semaphore_signal(sem);
19                 }
20             }];
21         }];
22         //如果全部請求沒有返回則該線程會一直阻塞
23         dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
24         NSLog(@"all http request done! end thread: %@", [NSThread currentThread]);
25         dispatch_async(dispatch_get_main_queue(), ^{
26             NSLog(@"UI update in main thread!");
27         });
28     });
29 
30 }

 

這是種可行的方法,思路很簡單:任務分發線程進行等待,所有網路請求成功返回後才發送信號量,任務分發線程繼續執行。直接上結果:

 1 2017-04-29 22:25:45.498 TestMutiRequest[1469:105980] start all http dispatch in thread: <NSThread: 0x608000260680>{number = 3, name = (null)}
 2 2017-04-29 22:25:45.499 TestMutiRequest[1469:106008] request:requestcommand3 run in thread:<NSThread: 0x60000007ec80>{number = 6, name = (null)}
 3 2017-04-29 22:25:45.499 TestMutiRequest[1469:105983] request:requestcommand2 run in thread:<NSThread: 0x608000260c00>{number = 5, name = (null)}
 4 2017-04-29 22:25:45.499 TestMutiRequest[1469:105981] request:requestcommand1 run in thread:<NSThread: 0x60000007e000>{number = 4, name = (null)}
 5 2017-04-29 22:25:45.499 TestMutiRequest[1469:106009] request:requestcommand4 run in thread:<NSThread: 0x608000260d40>{number = 7, name = (null)}
 6 2017-04-29 22:25:45.499 TestMutiRequest[1469:106010] request:requestcommand5 run in thread:<NSThread: 0x608000260b80>{number = 8, name = (null)}
 7 2017-04-29 22:25:45.519 TestMutiRequest[1469:105944] requset:requestcommand1 done!
 8 2017-04-29 22:25:47.500 TestMutiRequest[1469:105944] requset:requestcommand4 done!
 9 2017-04-29 22:25:49.559 TestMutiRequest[1469:105944] requset:requestcommand3 done!
10 2017-04-29 22:25:50.558 TestMutiRequest[1469:105944] requset:requestcommand5 done!
11 2017-04-29 22:25:52.571 TestMutiRequest[1469:105944] requset:requestcommand2 done!
12 2017-04-29 22:25:52.572 TestMutiRequest[1469:105980] all http request done! end thread: <NSThread: 0x608000260680>{number = 3, name = (null)}
13 2017-04-29 22:25:52.572 TestMutiRequest[1469:105944] UI update in main thread!

 

結論:
從效率和資源使用的角度來看,最後一種只使用信號量的方法是最好的,但是卻有點使用一個外部變數來保存執行次數的嫌疑。
另外本文的思路不僅適用於網路請求這種場景,其他需要非同步執行的類似場景也是適用的,比如資料庫操作等。
個人水平有限,如果你有更好的方案或者覺得不對的地方,隨時歡迎在評論留言交流學習!!

最後附上demo地址:
https://github.com/Calvix-Xu/TestMultiRequest.git



作者:Calvix
鏈接:http://www.jianshu.com/p/46f1314ed947
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...