有時候我們會碰到這樣子的一種情形: 同時獲取兩個網路請求的數據,但是網路請求是非同步的,我們需要獲取到兩個網路請求的數據之後才能夠進行下一步的操作,這個時候,就是線程組與信號量的用武之地了. 列印結果: 2016-03-15 04:01:53.279 NetWorking[83611:1508240]
有時候我們會碰到這樣子的一種情形:
同時獲取兩個網路請求的數據,但是網路請求是非同步的,我們需要獲取到兩個網路請求的數據之後才能夠進行下一步的操作,這個時候,就是線程組與信號量的用武之地了.
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線程組的使用,然後運行看列印結果。
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不是會在組中的非同步任務執行完畢了才會執行麽?這是什麼情況?
下麵我在上面的代碼基礎上添加了第33、38、39、49、54、55行代碼,然後我們再來看看列印結果:
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是國外的伺服器,但是沒關係,不要在意這個細節,列印順序還是一樣的):
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框架的底層代碼,而這部分代碼顯然是在主線程中執行的。
那麼,這時候,如果我們需要確定這個主線程中收到網路響應的數據被處理操作結束之後,才最後執行我們需要最後的操作的話,僅僅依靠線程組看來是不夠的,所以很少用到的GCD信號量就有了用武之地了。
當然,以上代碼如果不用GCD線程組,只用GCD的信號量來處理,也是可以的,這個就留給大家自己探究吧。
最後再簡化總結一下:信號量的使用前提是,想清楚你需要處理哪個線程等待,又要哪個線程繼續執行,然後使用信號量。
比如上面的AFN網路請求的示例,block回調是在main主線程中執行的,而get請求是在自己創建的非同步子線程中執行的。所以按照需求,就需要自己創建的非同步子線程等待main主線程中的block執行完了之後再執行。所以非同步子線程需要信號量wait,main主線程就設置signal發送信號量。