提起ios中多個(gè)異步函數(shù)后的同步問題,自然會想到 dispatch group 這個(gè)概念,那么它能夠解決異步網(wǎng)絡(luò)請求的問題嗎?
對于dispatch多個(gè)異步操作后的同步方法,以前只看過dispatch_group_async,看看這個(gè)方法的說明:
- @discussion
- Submits a block to a dispatch queue and associates the block with the given
- dispatch group. The dispatch group may be used to wait for the completion
- of the blocks it references.
可以看出,dispatch_group_async,是用于同步工作的,但是,它的判斷標(biāo)準(zhǔn)是放入的block是否執(zhí)行完畢,如果我們放入block中包含異步的網(wǎng)絡(luò)請求,這個(gè)方法無法在網(wǎng)絡(luò)數(shù)據(jù)返回后再進(jìn)行同步。
看一段使用dispatch_group_async處理網(wǎng)絡(luò)問題的代碼:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSURLSession *session = [NSURLSession sharedSession];
dispatch_queue_t dispatchQueue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"got data from internet1");
}];
[task resume];
});
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"got data from internet2");
}];
[task resume];
});
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"end");
});
}
看看log的輸出:
2016-07-13 17:42:55.170 aaaa[4797:295528] end
2016-07-13 17:42:55.322 aaaa[4797:295574] got data from internet2
2016-07-13 17:42:55.375 aaaa[4797:295574] got data from internet1
完全沒有達(dá)到效果。這是因?yàn)檫@里的網(wǎng)絡(luò)請求是個(gè)異步的方法,沒有等待具體的數(shù)據(jù)返回,放入的dispatch queue的 block就執(zhí)行完畢了。所以沒收到2個(gè)網(wǎng)絡(luò)數(shù)據(jù),就提前調(diào)用了dispatch_group_notify指定的結(jié)束方法。
看完了錯(cuò)誤的方法,再看看正確的方法:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSURLSession *session = [NSURLSession sharedSession];
dispatch_queue_t dispatchQueue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t dispatchGroup = dispatch_group_create();
// dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
dispatch_group_enter(dispatchGroup);
NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"got data from internet1");
dispatch_group_leave(dispatchGroup);
}];
[task resume];
// });
// dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
dispatch_group_enter(dispatchGroup);
NSURLSessionDataTask *task2 = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"got data from internet2");
dispatch_group_leave(dispatchGroup);
}];
[task2 resume];
// });
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"end");
});
}
看正確是輸出結(jié)果:
2016-07-13 17:46:10.282 aaaa[4847:300370] got data from internet1
2016-07-13 17:46:10.501 aaaa[4847:300370] got data from internet2
2016-07-13 17:46:10.502 aaaa[4847:300341] end
相對于簡單的dispatch_group_async,dispatch_group_enter 和 dispatch_group_leave 可以對group進(jìn)行更細(xì)致的處理。
我們看看關(guān)于dispatch_group_enter 的具體說明
- Calling this function increments the current count of outstanding tasks in the group. Using this function (with dispatch_group_leave) allows your application to properly manage the task reference count if it explicitly adds and removes tasks from the group by a means other than using the dispatch_group_async function. A call to this function must be balanced with a call to dispatch_group_leave. You can use this function to associate a block with more than one group at the same time.
簡單的說,就是dispatch_group_enter會對group的內(nèi)部計(jì)數(shù)加一,dispatch_group_leave會對group的內(nèi)部計(jì)數(shù)減一,就類似以前的retain和release方法。說白了也是維護(hù)了一個(gè)計(jì)數(shù)器。
以前我的做法就是自己維護(hù)計(jì)數(shù)器。在發(fā)送網(wǎng)絡(luò)請求前,記下發(fā)送總數(shù),數(shù)據(jù)返回后,在同一個(gè)thread中(或者在一個(gè)DISPATCH_QUEUE_SERIAL類型的dispatch_queue中),對計(jì)數(shù)器進(jìn)行+1操作,當(dāng)計(jì)數(shù)器和網(wǎng)絡(luò)請求數(shù)相等時(shí),調(diào)用最后的處理。
相比自己的處理的計(jì)數(shù)器,dispatch_group_enter 處理方法可能顯得更正規(guī)一些,代碼更規(guī)范了,但執(zhí)行效果是一樣的。。。
今天再改其他的工程的時(shí)候,又遇到了這個(gè)問題,有一個(gè)值,需要2個(gè)異步操作查詢回2個(gè)值進(jìn)行計(jì)算,因此必須再2個(gè)異步操作結(jié)束后才能進(jìn)行計(jì)算操作。開始試著使用了OperationQueue,想用addDependency方法,但是這個(gè)方法無法靈活地控制,只適合block內(nèi)容已經(jīng)確定的情況。對于我遇到的這種異步操作,block的內(nèi)容是不定的,需要依賴異步的返回,用operation queue會遇到各種問題,無法解決問題,十分復(fù)雜!
今天看到了dispatch_barrier_async函數(shù),說明如下
Calls to this function always return immediately after the block has been submitted and never wait for the block to be invoked. When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the barrier block executes by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.
The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.
簡單地說,就是在這個(gè)函數(shù)之前被提交到quque里的block一定會被先執(zhí)行,之后執(zhí)行dispatch_barrier_async設(shè)定的block,最后執(zhí)行調(diào)用dispatch_barrier_async之后才提交到queue里的block。
開始以為這個(gè)函數(shù)能夠處理我們的問題,結(jié)果是不行的,先看看測試用的代碼:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSURLSession *session = [NSURLSession sharedSession];
dispatch_queue_t dispatchQueue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(dispatchQueue, ^{
NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"got data from internet1");
}];
[task resume];
});
dispatch_async(dispatchQueue, ^{
NSURLSessionDataTask *task2 = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"got data from internet2");
}];
[task2 resume];
});
dispatch_barrier_async(dispatchQueue, ^{
NSLog(@"================== barrier block is called");
});
dispatch_async(dispatchQueue, ^{
NSLog(@"=========the last block is called");
});
}
這段代碼的輸出結(jié)果是:
2016-07-17 14:34:46.620 aaaaa[5000:91010] ================== barrier block is called
2016-07-17 14:34:46.621 aaaaa[5000:91010] =========the last block is called
2016-07-17 14:34:46.815 aaaaa[5000:91010] got data from internet1
2016-07-17 14:34:46.866 aaaaa[5000:91014] got data from internet2
完全沒有達(dá)到2個(gè)網(wǎng)絡(luò)請求都返回后,再執(zhí)行the last block的效果。
原因和 dispatch_group_async無法達(dá)到目的的原因是一樣的:它認(rèn)為一個(gè)block返回后就是邏輯結(jié)束了,就會繼續(xù)執(zhí)行其他代碼,對于block中異步返回的網(wǎng)絡(luò)數(shù)據(jù),沒有對應(yīng)的處理手段。
NSUrlSession 不是用的 NSOperation Queue 嗎,能不能直接利用Operation Queue 而不是dispatch_queue 來解決這個(gè)問題呢?
我們知道NSOperation中有addDependency這個(gè)方法,我們能不能把幾個(gè)網(wǎng)絡(luò)請求分別封裝一下:
[NSBlockOperation blockOperationWithBlock:^{
NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"got data from internet1");
}];
[task resume];
}];
這樣,再添加依賴,來達(dá)到效果呢?
結(jié)論是:僅僅使用NSBlockOperation來構(gòu)建operation是不可以的。 這里的錯(cuò)誤原因和使用dispatch_group_async是一樣的。
但是,如果把NSUrlConnection的請求封裝成NSOperation子類,使這個(gè)子類有這個(gè)效果:"當(dāng)網(wǎng)絡(luò)數(shù)據(jù)返回時(shí),才算這個(gè)operation的結(jié)束",就可以利用這個(gè)子類和nsoperationqueue 達(dá)到我們的目的!
(題外話:NSOperationQueue 就不同于dispatch_queue了,它沒有dispatch_queue中的并行,串行類型,但是,有個(gè)類似功能的屬性maxConcurrentOperationCount,當(dāng)maxConcurrentOperationCount = 1時(shí),自然就是串行的了。)
總結(jié)使用urlsession 進(jìn)行下載的通用方法,這個(gè)方法加入了對最大并發(fā)數(shù)的限制,也加入了全部完成后的回調(diào),基本可以應(yīng)對任何情況的下載了!
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
dispatch_group_t dispatchGroup = dispatch_group_create();
for (int i = 0; i < 10; i++)
{
NSLog(@"i is %d",i);
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:@"https://codeload.github.com/EricForGithub/SalesforceReactDemo/zip/master"];
NSURLSessionDownloadTask *sessionDownloadTask =[session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
sleep(5.0);
NSLog(@"release a signal");
dispatch_group_leave(dispatchGroup);
dispatch_semaphore_signal(semaphore);
}];
dispatch_group_enter(dispatchGroup);//為了所有下載完成后能調(diào)用函數(shù),引入 dispatch group。如果信號量是1的話,可以不使用這個(gè)機(jī)制,也能達(dá)到效果。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //為了最大并發(fā)數(shù),加入信號量機(jī)制
[sessionDownloadTask resume];
}
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"end");
});
}