前言
最近項目在接入網(wǎng)絡(luò)接口時, 有個比較值得注意的地方, 就是一個界面存在多個網(wǎng)絡(luò)接口, 比如: 在首頁界面中存在多個網(wǎng)絡(luò)請求接口(包括: 圖片輪播器, 黃金產(chǎn)品以及黃金樣品保單). 如果按照平常那種思維來編碼, 很可能會出現(xiàn)UI刷新不出來的問題. 造成界面空白現(xiàn)象. 我先說一下背景: 公司使用的網(wǎng)絡(luò)類是對AFNetworking框架進(jìn)行了再度封裝, 雖然大家都對它再熟悉不過了, 但是細(xì)節(jié)上的東西還是需要慢慢品味. 本章文章主要涉及到的是GCD中幾個比較常見的函數(shù).重在基礎(chǔ), 大神可以忽略. 如果文章中存在問題, 希望大神在底部留言, 指導(dǎo)一下小白.
思路: 首頁中后臺提供了三個網(wǎng)絡(luò)請求接口, 目的就是當(dāng)所有的請求操作都完成之后, 才去刷新界面, 顯示界面. 腦袋中一閃而過的是GCD中的隊列組, 將請求操作添加隊列組, 最后在dispatch_group_notify中刷新UI.
eg: (注意: 在dispatch_group_notify中打印的順序是隨機(jī)的)
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"比如說這里是在子線程上的第一個請求");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"比如說這里是子線程上的第二個請求");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"比如說這里是子線程上的第三個請求");
});
// 執(zhí)行完畢之后的通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"需要在這里回到主線程刷新UI");
});
- 我們先看看打印結(jié)果, 看看結(jié)果中有沒有什么貓膩
打印結(jié)果:
2017-02-15 09:22:08.287 text[987:26314] 比如說這里是子線程上的第二個請求
2017-02-15 09:22:08.287 text[987:26311] 比如說這里是在子線程上的第一個請求
2017-02-15 09:22:08.287 text[987:26312] 比如說這里是子線程上的第三個請求
2017-02-15 09:22:08.302 text[987:26273] 需要在這里回到主線程刷新UI
解釋: 單純這個例子中,很難看出什么端倪, 但是這時候需要想到在開發(fā)中使用場景是網(wǎng)絡(luò)請求. 可能出現(xiàn)的情況: 在實際開發(fā)中, 我使用的AFN框架來實現(xiàn)網(wǎng)絡(luò)請求, AFN中的網(wǎng)絡(luò)請求都是異步操作, 就是說請求的數(shù)據(jù)返回后, 才會去刷新相關(guān)的UI
如果請求操作有多個, 所以必須要所有的操作都完成之后, 才去刷新UI,這樣就可能會造成一個現(xiàn)象, 就是數(shù)據(jù)是返回了, 但是刷新后UI不顯示.最后導(dǎo)致界面空白無物.
- 解決方法
解決方法: 根據(jù)上述的現(xiàn)象, 這里需要引進(jìn)另一個函數(shù)
dispatch_semaphore(即: GCD中的信號量), 通過GCD中的信號量實現(xiàn)線程同步
dispatch_semaphore概念: 信號量是基于計數(shù)器的一種多線程同步機(jī)制, 主要是用于解決多個線程訪問共有的資源時.造成數(shù)據(jù)紊亂的問題.
dispatch_semaphore基本原理: 比如說網(wǎng)絡(luò)請求成功或者失敗之后需要將dispatch_semaphore計數(shù)器 +1, 請求網(wǎng)絡(luò)操作完成之后需要將dispatch_semaphore計數(shù)器 -1. 如果dispatch_semaphore計數(shù)器等于0表示等待.
加1操作: dispatch_semaphore_signal(semaphore)
等待操作: dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)舉個例子: 每當(dāng)有顧客點餐,計數(shù)+1硝桩,點餐結(jié)束-1歸零繼續(xù)等待下一位顧客。比較類似于NSLock(線程鎖)。
eg:
- (void)request_A {
//創(chuàng)建信號量并設(shè)置計數(shù)默認(rèn)為0
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSDictionary *parameter = @{@"key":@"value"
};
[manager POST:URL parameters:parameter progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//計數(shù)+1操作
dispatch_semaphore_signal(sema);
NSLog(@"在這里獲取數(shù)據(jù)");
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
////計數(shù)+1操作
dispatch_semaphore_signal(sema);
NSLog(@"在這里獲取error");
}];
//若計數(shù)為0則一直等待
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
為了便于記住, 一下是簡寫
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網(wǎng)絡(luò)請求:{
成功:dispatch_semaphore_signal(sema);
失敗:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
// 強行解釋一波
通過使用GCD中的信號量可以解決多個操作共用同一資源時, 造成主線程阻塞的問題.
知識擴(kuò)展
- 擴(kuò)展1: GCD中提供了函數(shù), 可以指定操作的執(zhí)行順序
> 擴(kuò)展: 如果我們要指定網(wǎng)絡(luò)操作的執(zhí)行順序的話, 直接使用GCD中的隊列, 然后添加依賴即可.
eg:
//1.任務(wù)一:
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" //1.任務(wù)一:")
}];
//2.任務(wù)二:
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" //1.任務(wù)二:")
}];
//3.任務(wù)三:
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" //1.任務(wù)三:")
}];
//4.設(shè)置依賴
[operation2 addDependency:operation1]; //任務(wù)二依賴任務(wù)一
[operation3 addDependency:operation2]; //任務(wù)三依賴任務(wù)二
//5.創(chuàng)建隊列并加入任務(wù)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
- 擴(kuò)展2: 在Objctive-C中GCD提供了兩種方式來支持dispatch隊列的同步,就是dispatch組(隊列組)和信號量(dispatch_semaphore)
// 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
// 啟動隊列組中的block, 然后關(guān)聯(lián)到隊列組group中,
隊列組的block操作
@param group 隊列組
@param queue#> 可以是全局的dispatch_get_global_queue(0, 0), 也可以是dispatch_get_main_queue()
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"具體的網(wǎng)絡(luò)請求操作");
});
// 超時參數(shù)
#define DISPATCH_TIME_NOW (0ull) // 現(xiàn)在
#define DISPATCH_TIME_FOREVER (~0ull) // 一直
// 表示: 等到group關(guān)聯(lián)的block執(zhí)行完畢
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 當(dāng)group組執(zhí)行完畢之后, 需要通知執(zhí)行完畢, 那么就會使用到GCD中的dispatch_barrier_async函數(shù)
// 主要隊列組中的操作執(zhí)行完畢后就會調(diào)用這個函數(shù)中block
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"隊列組中的操作執(zhí)行完畢之后就會調(diào)用該函數(shù)");
});
// 我們也可以管理隊列組的運行狀態(tài)或者計數(shù), 使用下面兩個函數(shù)的時候需要注意的一點就是, 進(jìn)入或者退出, 他們的次數(shù)必須要匹配.
dispatch_group_enter(group); // 進(jìn)入
dispatch_group_leave(group); // 退出
// 所以卡睦,我們也可以利用dispatch_group_enter币旧、 dispatch_group_leave和dispatch_group_wait來實現(xiàn)同步
- 信號量
二、dispatch信號量(dispatch semaphore)
1. 創(chuàng)建信號量稀颁,可以設(shè)置信號量的資源數(shù)。0表示沒有資源楣黍,調(diào)用dispatch_semaphore_wait會立即等待匾灶。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
2. 等待信號,可以設(shè)置超時參數(shù)租漂。該函數(shù)返回0表示得到通知阶女,非0表示超時。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
3. 通知信號哩治,如果等待線程被喚醒則返回非0秃踩,否則返回0。
dispatch_semaphore_signal(semaphore);
最后业筏,還是回到生成消費者的例子憔杨,使用dispatch信號量是如何實現(xiàn)同步: