有時候咱們會碰到這樣子的一種情形:html
同時獲取兩個網絡請求的數據,可是網絡請求是異步的,咱們須要獲取到兩個網絡請求的數據以後纔可以進行下一步的操做,這個時候,就是線程組與信號量的用武之地了.api
1 #import "ViewController.h" 2 #import <AFNetworking.h> 3 4 5 @interface ViewController () 6 7 @end 8 9 @implementation ViewController 10 11 - (void)viewDidLoad { 12 [super viewDidLoad]; 13 [self getNetworkingData]; 14 } 15 16 - (void)getNetworkingData{ 17 NSString *appIdKey = @"8781e4ef1c73ff20a180d3d7a42a8c04"; 18 NSString* urlString_1 = @"http://api.openweathermap.org/data/2.5/weather"; 19 NSString* urlString_2 = @"http://api.openweathermap.org/data/2.5/forecast/daily"; 20 NSDictionary* dictionary =@{@"lat":@"40.04991291", 21 @"lon":@"116.25626162", 22 @"APPID" : appIdKey}; 23 // 建立組 24 dispatch_group_t group = dispatch_group_create(); 25 // 將第一個網絡請求任務添加到組中 26 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 27 // 建立信號量 28 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 29 // 開始網絡請求任務 30 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 31 [manager GET:urlString_1 32 parameters:dictionary 33 progress:nil 34 success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 35 NSLog(@"成功請求數據1:%@",[responseObject class]); 36 // 若是請求成功,發送信號量 37 dispatch_semaphore_signal(semaphore); 38 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 39 NSLog(@"失敗請求數據"); 40 // 若是請求失敗,也發送信號量 41 dispatch_semaphore_signal(semaphore); 42 }]; 43 // 在網絡請求任務成功以前,信號量等待中 44 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 45 }); 46 // 將第二個網絡請求任務添加到組中 47 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 48 // 建立信號量 49 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 50 // 開始網絡請求任務 51 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 52 [manager GET:urlString_2 53 parameters:dictionary 54 progress:nil 55 success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 56 NSLog(@"成功請求數據2:%@",[responseObject class]); 57 // 若是請求成功,發送信號量 58 dispatch_semaphore_signal(semaphore); 59 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 60 NSLog(@"失敗請求數據"); 61 // 若是請求失敗,也發送信號量 62 dispatch_semaphore_signal(semaphore); 63 }]; 64 // 在網絡請求任務成功以前,信號量等待中 65 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 66 }); 67 dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 68 NSLog(@"完成了網絡請求,無論網絡請求失敗了仍是成功了。"); 69 }); 70 } 71 72 @end
打印結果:服務器
2016-03-15 04:01:53.279 NetWorking[83611:1508240] 成功請求數據1:__NSCFDictionary
2016-03-15 04:01:53.280 NetWorking[83611:1508240] 成功請求數據2:__NSCFDictionary
2016-03-15 04:01:53.281 NetWorking[83611:1508287] 完成了網絡請求,無論網絡請求失敗了仍是成功了。網絡
爲了和上面造成對比,我特意將全部的信號量的代碼所有去除,可是保留GCD線程組的使用,而後運行看打印結果。app
1 #import "ViewController.h" 2 #import <AFNetworking.h> 3 4 5 @interface ViewController () 6 7 @end 8 9 @implementation ViewController 10 11 - (void)viewDidLoad { 12 [super viewDidLoad]; 13 [self getNetworkingData]; 14 } 15 16 - (void)getNetworkingData{ 17 NSString *appIdKey = @"8781e4ef1c73ff20a180d3d7a42a8c04"; 18 NSString* urlString_1 = @"http://api.openweathermap.org/data/2.5/weather"; 19 NSString* urlString_2 = @"http://api.openweathermap.org/data/2.5/forecast/daily"; 20 NSDictionary* dictionary =@{@"lat":@"40.04991291", 21 @"lon":@"116.25626162", 22 @"APPID" : appIdKey}; 23 // 建立組 24 dispatch_group_t group = dispatch_group_create(); 25 // 將第一個網絡請求任務添加到組中 26 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 27 // 開始網絡請求任務 28 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 29 [manager GET:urlString_1 30 parameters:dictionary 31 progress:nil 32 success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 33 NSLog(@"成功請求數據1:%@",[responseObject class]); 34 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 35 NSLog(@"失敗請求數據"); 36 }]; 37 }); 38 // 將第二個網絡請求任務添加到組中 39 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 40 // 開始網絡請求任務 41 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 42 [manager GET:urlString_2 43 parameters:dictionary 44 progress:nil 45 success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 46 NSLog(@"成功請求數據2:%@",[responseObject class]); 47 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 48 NSLog(@"失敗請求數據"); 49 }]; 50 }); 51 dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 52 NSLog(@"完成了網絡請求,無論網絡請求失敗了仍是成功了。"); 53 }); 54 } 55 56 @end
打印結果:框架
2016-03-15 04:05:09.378 NetWorking[83698:1510242] 完成了網絡請求,無論網絡請求失敗了仍是成功了。
2016-03-15 04:05:10.185 NetWorking[83698:1510096] 成功請求數據1:__NSCFDictionary
2016-03-15 04:05:10.186 NetWorking[83698:1510096] 成功請求數據2:__NSCFDictionary異步
看到這個打印結果,咱們彷佛有點看不懂了,難道notify線程組沒用了?notify不是會在組中的異步任務執行完畢了纔會執行麼?這是什麼狀況?async
下面我在上面的代碼基礎上添加了第3三、3八、3九、4九、5四、55行代碼(也就是下面紅色高亮的幾行代碼,都是打印當前線程),而後咱們再來看看打印結果:url
1 #import "ViewController.h" 2 #import <AFNetworking.h> 3 4 5 @interface ViewController () 6 7 @end 8 9 @implementation ViewController 10 11 - (void)viewDidLoad { 12 [super viewDidLoad]; 13 [self getNetworkingData]; 14 } 15 16 - (void)getNetworkingData{ 17 NSString *appIdKey = @"8781e4ef1c73ff20a180d3d7a42a8c04"; 18 NSString* urlString_1 = @"http://api.openweathermap.org/data/2.5/weather"; 19 NSString* urlString_2 = @"http://api.openweathermap.org/data/2.5/forecast/daily"; 20 NSDictionary* dictionary =@{@"lat":@"40.04991291", 21 @"lon":@"116.25626162", 22 @"APPID" : appIdKey}; 23 // 建立組 24 dispatch_group_t group = dispatch_group_create(); 25 // 將第一個網絡請求任務添加到組中 26 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 27 // 開始網絡請求任務 28 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 29 [manager GET:urlString_1 30 parameters:dictionary 31 progress:nil 32 success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 33 NSLog(@"%@",[NSThread currentThread]); 34 NSLog(@"成功請求數據1:%@",[responseObject class]); 35 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 36 NSLog(@"失敗請求數據"); 37 }]; 38 NSLog(@"%@",[NSThread currentThread]); 39 NSLog(@"AFN網絡請求框架請求完畢"); 40 }); 41 // 將第二個網絡請求任務添加到組中 42 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 43 // 開始網絡請求任務 44 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 45 [manager GET:urlString_2 46 parameters:dictionary 47 progress:nil 48 success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 49 NSLog(@"%@",[NSThread currentThread]); 50 NSLog(@"成功請求數據2:%@",[responseObject class]); 51 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 52 NSLog(@"失敗請求數據"); 53 }]; 54 NSLog(@"%@",[NSThread currentThread]); 55 NSLog(@"AFN網絡請求框架請求完畢"); 56 }); 57 dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 58 NSLog(@"完成了網絡請求,無論網絡請求失敗了仍是成功了。"); 59 }); 60 } 61 62 @end
打印結果(舒適提示:請求數據可能會出現失敗,由於這個網絡請求的url是國外的服務器,可是不要緊,不要在乎這個細節,打印順序仍是同樣的):spa
2016-03-15 04:30:07.406 NetWorking[84306:1523047] <NSThread: 0x7fc258725e10>{number = 2, name = (null)}
2016-03-15 04:30:07.406 NetWorking[84306:1523048] <NSThread: 0x7fc258406100>{number = 3, name = (null)}
2016-03-15 04:30:07.407 NetWorking[84306:1523047] AFN網絡請求框架請求完畢
2016-03-15 04:30:07.407 NetWorking[84306:1523048] AFN網絡請求框架請求完畢
2016-03-15 04:30:07.407 NetWorking[84306:1523075] 完成了網絡請求,無論網絡請求失敗了仍是成功了。
2016-03-15 04:30:08.239 NetWorking[84306:1523016] <NSThread: 0x7fc258507af0>{number = 1, name = main}
2016-03-15 04:30:08.239 NetWorking[84306:1523016] 成功請求數據1:__NSCFDictionary
2016-03-15 04:30:08.240 NetWorking[84306:1523016] <NSThread: 0x7fc258507af0>{number = 1, name = main}
2016-03-15 04:30:08.240 NetWorking[84306:1523016] 成功請求數據2:__NSCFDictionary
總結:網絡請求而後處理響應數據是個耗時的操做,也是咱們開發中常見的一種情形,在網絡請求以及處理響應數據操做完畢以後咱們在執行別的操做這樣的過程也是咱們開發中常見的情形。根據第三部分代碼(沒有使用信號量的代碼)打印結果的順序,咱們能夠知道,網絡請求的任務是提交給子線程異步處理了,網絡請求這樣的任務也就快速執行完畢了,可是網絡請求是一個任務,處理收到的網絡響應又是一個任務,注意不要把這兩個過程混爲一談。而收到網絡響應以及處理返回響應的數據並非在子線程中執行的,咱們經過在回調響應處理的block(好比48~53行之間就有兩個block)中打印當前線程,會發現回調響應處理的block是在主線程中被執行的。
若是讀者很熟悉block回調這種通訊機制的話,就不難理解,這個回調響應的block真正被調用執行的地方應該是AFN框架的底層代碼,AFN 底層代碼,是在獲取到最終數據後,執行回調操做,此時,才能拿到相應數據,而這個處理網絡響應的過程,AFN底層最終是這麼作的:
也就是說,seccess和failure都是在主線中異步任務中執行的。
那麼,這時候,若是咱們須要肯定這個主線程中收到網絡響應的數據被處理操做結束以後,才最後執行咱們須要最後的操做的話,僅僅依靠線程組看來是不夠的,因此不多用到的GCD信號量就有了用武之地了。
固然,以上代碼若是不用GCD線程組,只用GCD的信號量來處理,也是能夠的,這個就留給你們本身探究吧。
最後再簡化總結一下:信號量的使用前提是,想清楚你須要處理哪一個線程等待,又要哪一個線程繼續執行,而後使用信號量。
好比上面的AFN網絡請求的示例,block回調是在main主線程中執行的,而get請求是在本身建立的異步子線程中執行的。因此按照需求,就須要本身建立的異步子線程等待main主線程中的block執行完了以後再執行。因此異步子線程須要信號量wait,main主線程就設置signal發送信號量。