使用GCD實(shí)現(xiàn)和封裝分組并發(fā)網(wǎng)絡(luò)請(qǐng)求

為什么使用并發(fā)組請(qǐng)求?


在實(shí)際開(kāi)發(fā)中我們通常會(huì)遇到這樣一種需求:某個(gè)頁(yè)面加載時(shí)通過(guò)網(wǎng)絡(luò)請(qǐng)求獲得相應(yīng)的數(shù)據(jù),再做某些操作,有時(shí)候加載的內(nèi)容需要通過(guò)好幾個(gè)請(qǐng)求的數(shù)據(jù)組合而成,比如有兩個(gè)請(qǐng)求A和B,我們通常為了省事,會(huì)將B請(qǐng)求放在A請(qǐng)求成功的回調(diào)中發(fā)起,在B的成功回調(diào)中將數(shù)據(jù)組合起來(lái),這樣做有明顯的問(wèn)題:

  1. 請(qǐng)求如果多了,需要寫(xiě)許多嵌套的請(qǐng)求
  2. 如果在除了最后一個(gè)請(qǐng)求前的某個(gè)請(qǐng)求失敗了,就不會(huì)執(zhí)行后面的請(qǐng)求,數(shù)據(jù)無(wú)法加載
  3. 請(qǐng)求變成同步的,這是最大的問(wèn)題,在網(wǎng)絡(luò)差的情況下,如果有n個(gè)請(qǐng)求,意味著用戶要等待n倍于并發(fā)請(qǐng)求的時(shí)間才能看到內(nèi)容

dispatch_group并發(fā)組


熟悉dispatch_group的同學(xué)可以直接跳過(guò)這一節(jié)。

同步請(qǐng)求這么low的方式當(dāng)然是不可接受的,所以我們要并發(fā)這些請(qǐng)求,在所有請(qǐng)求都執(zhí)行完成功回調(diào)后,再做加載內(nèi)容或其他操作,考慮再三,選擇用GCD的dispatch_group最方便。

A dispatch group is a mechanism for monitoring a set of blocks. Your application can monitor the blocks in the group synchronously or asynchronously depending on your needs. By extension, a group can be useful for synchronizing for code that depends on the completion of other tasks.

可以看出,dispatch_group專(zhuān)為監(jiān)控block而生,并且蘋(píng)果也建議當(dāng)你的某個(gè)操作依賴(lài)于其他幾個(gè)任務(wù)的完成時(shí),可以使用dispatch_group。

dispatch_group通常有兩種用法:

  1. dispatch_group_async(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
    創(chuàng)建一個(gè)dispatch_group_t, 將并發(fā)的操作放在block中,在dispatch_group_notify(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)的block中執(zhí)行多組block執(zhí)行完畢后的操作,對(duì)于網(wǎng)絡(luò)請(qǐng)求來(lái)說(shuō),在請(qǐng)求發(fā)出時(shí)他就算執(zhí)行完畢了,也就是block中還有個(gè)block的情況下,并不會(huì)等待網(wǎng)絡(luò)請(qǐng)求的回調(diào),所以不滿足我們的需求。
    所以采用另一種用法:

  2. dispatch_group_enter(<#dispatch_group_t group#>)
    dispatch_group_leave(<#dispatch_group_t group#>)
    以下是dispatch_group_enter的官方文檔解釋?zhuān)?/p>

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實(shí)際上有一個(gè)task reference count(任務(wù)計(jì)數(shù)器),enter時(shí)reference count +1,leave時(shí)reference count -1,enter和leave必須配合使用,有幾次enter就要有幾次leave,否則group會(huì)一直存在,dispatch_group_notify也不會(huì)觸發(fā)。

當(dāng)所有enter的block都leave后,會(huì)執(zhí)行dispatch_group_notify的block,這種方式顯然更加靈活。

我們當(dāng)然可以在網(wǎng)絡(luò)請(qǐng)求前enter,在執(zhí)行完每個(gè)請(qǐng)求的成功或失敗回調(diào)后leave,再在notify中執(zhí)行內(nèi)容加載。至此,并發(fā)網(wǎng)絡(luò)組請(qǐng)求的問(wèn)題就解決了,但還是有點(diǎn)小小不爽,每次發(fā)起組請(qǐng)求我都得創(chuàng)建group,寫(xiě)一堆的enter和leave,既麻煩也不利于請(qǐng)求的復(fù)用,很自然我們想到把他封裝一下,最好能做到將一個(gè)網(wǎng)絡(luò)請(qǐng)求加到組里,而不用修改原先的網(wǎng)絡(luò)請(qǐng)求代碼,就像這樣:

[[NetworkTool sharedInstance] postForGroup:^{
        [request1 success:^(id responseObject) {

        } failure:^(NSError *error) {

        }];
        [request2 success:^(id responseObject) {

        } failure:^(NSError *error) {

        }];
    } success:^{
        // group success
    } failure:^(NSArray *errorArray) {
        // group failure
    }];

組請(qǐng)求的封裝


如果我想做到這種效果,肯定要到網(wǎng)絡(luò)單例層去對(duì)底層請(qǐng)求做些修改,但我又不想改變現(xiàn)有的底層請(qǐng)求方法,所以我采用了method_exchangeImplementations(<#Method m1#>, <#Method m2#>)這個(gè)函數(shù),基于現(xiàn)有的底層請(qǐng)求方法,實(shí)現(xiàn)一套組的請(qǐng)求方法。在發(fā)組請(qǐng)求時(shí),替換掉原先的方法,在組請(qǐng)求都發(fā)送完畢后,再換回原先的方法。

但這里有一些可怕的坑要處理,因?yàn)槭褂梅椒ㄌ鎿Q是很危險(xiǎn)的。

  1. 我做了替換后,正常的非組網(wǎng)絡(luò)請(qǐng)求也會(huì)走替換后的方法,但我不需要他走替換后的方法。
  2. 假如我同時(shí)發(fā)起了多個(gè)組請(qǐng)求,組和組之間要如何區(qū)分,不同的組是不應(yīng)該相互影響的。

一開(kāi)始我考慮給請(qǐng)求一個(gè)mark,標(biāo)記他是屬于哪個(gè)group的,但這需要你已經(jīng)把請(qǐng)求封裝成了一個(gè)對(duì)象,如果你的項(xiàng)目和我的一樣,發(fā)請(qǐng)求時(shí)只是執(zhí)行一個(gè)方法,是不好給他加標(biāo)記的。

在一陣頭腦風(fēng)暴后,我決定用隊(duì)列來(lái)區(qū)分每個(gè)gorup。

具體做法就是創(chuàng)建group時(shí),開(kāi)啟一個(gè)隊(duì)列,給隊(duì)列動(dòng)態(tài)添加group屬性,一個(gè)隊(duì)列對(duì)應(yīng)一個(gè)group。在隊(duì)列中替換方法,發(fā)起組里的請(qǐng)求,再替換回原先的方法。這樣在替換的方法里只需要拿到當(dāng)前的隊(duì)列,就可以拿到group,如果group是nil,說(shuō)明是正常的非組請(qǐng)求,執(zhí)行original method;如果group不是nil,根據(jù)group來(lái)enter和leave,這樣每個(gè)group也能區(qū)分開(kāi)。

創(chuàng)建group時(shí),給group動(dòng)態(tài)添加一個(gè)errorArray屬性,用來(lái)記錄組里請(qǐng)求的error,只要errorArray不為空,就會(huì)走組失敗的block。

附上完整代碼:

typedef void(^BlockAction)();
typedef void(^GroupResponseFailure)(NSArray * errorArray);
static char groupErrorKey;
static char queueGroupKey;

單例中用來(lái)替換底層網(wǎng)絡(luò)請(qǐng)求的組請(qǐng)求方法

- (void)sendPOSTRequestInGroup:(NSString *)strURL withData:(NSDictionary *)data paramForm:(ParamForm)paramForm withTimeout:(NSTimeInterval)timeout showAlert:(BOOL)show  success:(BlockResponse)success failure:(BlockResponseFailure)failure {
    
    dispatch_group_t group = objc_getAssociatedObject([NSOperationQueue currentQueue], &queueGroupKey);
    
    // 如果是非組請(qǐng)求
    if (group == nil) {
        // 執(zhí)行original method
        [self sendPOSTRequestInGroup:strURL withData:data paramForm:paramForm withTimeout:timeout showAlert:show success:success failure:failure];
        return;
    }
    
    dispatch_group_enter(group);
    // 執(zhí)行original method
    [self sendPOSTRequestInGroup:strURL withData:data paramForm:paramForm withTimeout:timeout showAlert:show success:^(id responseObject) {

        if (success) {
            success(responseObject);
        }

        dispatch_group_leave(group);
    } failure:^(NSError *error) {
        NSMutableArray *arrayM = objc_getAssociatedObject(group, &groupErrorKey);
        [arrayM addObject:error];

        if (failure) {
            failure(error);
        }

        dispatch_group_leave(group);
    }];
}

提供給外界的組請(qǐng)求方法

- (void)sendGroupPostRequest:(BlockAction)requests success:(BlockAction)success failure:(GroupResponseFailure)failure {
    if (requests == nil) {
        return;
    }
    
    dispatch_group_t group = dispatch_group_create();
    objc_setAssociatedObject(group, &groupErrorKey, [NSMutableArray array], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    Method originalPost = class_getInstanceMethod(self.class, @selector(sendPOSTRequest:withData:paramForm:withTimeout:showAlert:success:failure:));
    Method groupPost = class_getInstanceMethod(self.class, @selector(sendPOSTRequestInGroup:withData:paramForm:withTimeout:showAlert:success:failure:));
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    objc_setAssociatedObject(queue, &queueGroupKey, group, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    queue.qualityOfService = NSQualityOfServiceUserInitiated;
    queue.maxConcurrentOperationCount = 3;
    
    [queue addOperationWithBlock:^{
        
        method_exchangeImplementations(originalPost, groupPost);
        // 現(xiàn)在發(fā)起請(qǐng)求會(huì)調(diào)用上面的組請(qǐng)求方法
        requests();
        // 發(fā)出請(qǐng)求后就可以替換回original method,不必等待回調(diào),盡量減小替換的時(shí)間窗口
        method_exchangeImplementations(originalPost, groupPost);
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSMutableArray *arrayM = objc_getAssociatedObject(group, &groupErrorKey);
            // 只要組里的一個(gè)請(qǐng)求失敗,就走組失敗的回調(diào)
            if (arrayM.count > 0) {
                if (failure) {
                    failure(arrayM.copy);
                }
            } else if(success) {
                success();
            }
        });
    }];
}

經(jīng)過(guò)這一番封裝,我在使用時(shí),只需要在- (void)sendGroupPostRequest:(BlockAction)requests success:(BlockAction)success failure:(GroupResponseFailure)failure 這個(gè)方法的requests block中,把網(wǎng)絡(luò)請(qǐng)求扔進(jìn)去,原先寫(xiě)好的請(qǐng)求不用做任何修改,請(qǐng)求本身的success和failure也都能執(zhí)行,success block中寫(xiě)組成功后要做的事情,比如內(nèi)容加載,failure block中可以拿到每個(gè)請(qǐng)求的error,作相應(yīng)處理。

Demo在此:https://github.com/suruihai/-GCD-Demo/tree/master

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 在實(shí)際開(kāi)發(fā)中我們通常會(huì)遇到這樣一種需求:某個(gè)頁(yè)面加載時(shí)通過(guò)網(wǎng)絡(luò)請(qǐng)求獲得相應(yīng)的數(shù)據(jù),再做某些操作。有時(shí)候加載的...
    so_what閱讀 17,027評(píng)論 13 79
  • 在這兩部分的系列中,第一個(gè)部分的將解釋 GCD 是做什么的,并從許多基本的 GCD 函數(shù)中找出幾個(gè)來(lái)展示。在第二部...
    透支未來(lái)閱讀 405評(píng)論 0 1
  • 一、簡(jiǎn)單介紹下將會(huì)用到的一些東西 英語(yǔ)不好就不翻譯官方文檔了.. 1、dispatch_group_async S...
    Albert新榮閱讀 1,819評(píng)論 0 1
  • 一 遲到了兩個(gè)月的專(zhuān)四成績(jī),伴隨著開(kāi)學(xué)的腳步來(lái)到了我們的生活中,每個(gè)人都像一個(gè)戰(zhàn)斗已久的戰(zhàn)士等待著宣布勝負(fù),哪怕明...
    十四顆牙閱讀 513評(píng)論 0 0
  • 從前,在城堡里有一位美麗的公主,她叫:塔莉娜。塔莉娜早已對(duì)皇室生活產(chǎn)生了厭倦,她總是向往著自己有美好、自由的生活。...
    楊藝萱閱讀 1,056評(píng)論 0 0

友情鏈接更多精彩內(nèi)容