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

為什么使用并發(fā)組請求沐悦?


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

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

dispatch_group并發(fā)組


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

同步請求這么low的方式當(dāng)然是不可接受的刷允,所以我們要并發(fā)這些請求冤留,在所有請求都執(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專為監(jiān)控block而生泊窘,并且蘋果也建議當(dāng)你的某個操作依賴于其他幾個任務(wù)的完成時熄驼,可以使用dispatch_group像寒。

dispatch_group通常有兩種用法:

  1. dispatch_group_async(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
    創(chuàng)建一個dispatch_group_t, 將并發(fā)的操作放在block中瓜贾,在dispatch_group_notify(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)的block中執(zhí)行多組block執(zhí)行完畢后的操作诺祸,對于網(wǎng)絡(luò)請求來說,在請求發(fā)出時他就算執(zhí)行完畢了祭芦,也就是block中還有個block的情況下筷笨,并不會等待網(wǎng)絡(luò)請求的回調(diào),所以不滿足我們的需求龟劲。
    所以采用另一種用法:

  2. dispatch_group_enter(<#dispatch_group_t group#>)
    dispatch_group_leave(<#dispatch_group_t group#>)
    以下是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實際上有一個task reference count(任務(wù)計數(shù)器)胃夏,enter時reference count +1,leave時reference count -1昌跌,enter和leave必須配合使用仰禀,有幾次enter就要有幾次leave,否則group會一直存在蚕愤,dispatch_group_notify也不會觸發(fā)答恶。

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

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

[[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
    }];

組請求的封裝


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

但這里有一些可怕的坑要處理,因為使用方法替換是很危險的噩斟。

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

一開始我考慮給請求一個mark覆享,標(biāo)記他是屬于哪個group的,但這需要你已經(jīng)把請求封裝成了一個對象营袜,如果你的項目和我的一樣撒顿,發(fā)請求時只是執(zhí)行一個方法,是不好給他加標(biāo)記的荚板。

在一陣頭腦風(fēng)暴后凤壁,我決定用隊列來區(qū)分每個gorup。

具體做法就是創(chuàng)建group時跪另,開啟一個隊列拧抖,給隊列動態(tài)添加group屬性,一個隊列對應(yīng)一個group免绿。在隊列中替換方法唧席,發(fā)起組里的請求,再替換回原先的方法嘲驾。這樣在替換的方法里只需要拿到當(dāng)前的隊列淌哟,就可以拿到group,如果group是nil辽故,說明是正常的非組請求徒仓,執(zhí)行original method;如果group不是nil誊垢,根據(jù)group來enter和leave掉弛,這樣每個group也能區(qū)分開。

創(chuàng)建group時喂走,給group動態(tài)添加一個errorArray屬性殃饿,用來記錄組里請求的error,只要errorArray不為空缴啡,就會走組失敗的block壁晒。

附上完整代碼:

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

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

- (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);
    
    // 如果是非組請求
    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);
    }];
}

提供給外界的組請求方法

- (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ā)起請求會調(diào)用上面的組請求方法
        requests();
        // 發(fā)出請求后就可以替換回original method瓷们,不必等待回調(diào)业栅,盡量減小替換的時間窗口
        method_exchangeImplementations(originalPost, groupPost);
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSMutableArray *arrayM = objc_getAssociatedObject(group, &groupErrorKey);
            // 只要組里的一個請求失敗秒咐,就走組失敗的回調(diào)
            if (arrayM.count > 0) {
                if (failure) {
                    failure(arrayM.copy);
                }
            } else if(success) {
                success();
            }
        });
    }];
}

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姆坚,一起剝皮案震驚了整個濱河市澳泵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兼呵,老刑警劉巖兔辅,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異击喂,居然都是意外死亡维苔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門懂昂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來介时,“玉大人,你說我怎么就攤上這事凌彬〕背ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵饿序,是天一觀的道長勉失。 經(jīng)常有香客問我,道長原探,這世上最難降的妖魔是什么乱凿? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮咽弦,結(jié)果婚禮上徒蟆,老公的妹妹穿的比我還像新娘。我一直安慰自己型型,他們只是感情好段审,可當(dāng)我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著闹蒜,像睡著了一般寺枉。 火紅的嫁衣襯著肌膚如雪抑淫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天姥闪,我揣著相機與錄音始苇,去河邊找鬼。 笑死筐喳,一個胖子當(dāng)著我的面吹牛催式,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播避归,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼荣月,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了梳毙?” 一聲冷哼從身側(cè)響起喉童,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎顿天,沒想到半個月后堂氯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡牌废,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年咽白,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸟缕。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡晶框,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出懂从,到底是詐尸還是另有隱情授段,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布番甩,位于F島的核電站侵贵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏缘薛。R本人自食惡果不足惜窍育,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宴胧。 院中可真熱鬧漱抓,春花似錦、人聲如沸恕齐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至仪或,卻和暖如春确镊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背溶其。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工骚腥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敦间,地道東北人瓶逃。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像廓块,于是被迫代替她去往敵國和親厢绝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 前言 在實際開發(fā)中我們通常會遇到這樣一種需求:某個頁面加載時通過網(wǎng)絡(luò)請求獲得相應(yīng)的數(shù)據(jù)带猴,再做某些操作昔汉。有時候加載的...
    so_what閱讀 16,851評論 13 79
  • 在這兩部分的系列中,第一個部分的將解釋 GCD 是做什么的拴清,并從許多基本的 GCD 函數(shù)中找出幾個來展示靶病。在第二部...
    透支未來閱讀 335評論 0 1
  • 一群井、簡單介紹下將會用到的一些東西 英語不好就不翻譯官方文檔了.. 1湿刽、dispatch_group_async S...
    Albert新榮閱讀 1,702評論 0 1
  • 一 遲到了兩個月的專四成績,伴隨著開學(xué)的腳步來到了我們的生活中羹奉,每個人都像一個戰(zhàn)斗已久的戰(zhàn)士等待著宣布勝負(fù)沪停,哪怕明...
    十四顆牙閱讀 456評論 0 0
  • 從前煤辨,在城堡里有一位美麗的公主,她叫:塔莉娜木张。塔莉娜早已對皇室生活產(chǎn)生了厭倦众辨,她總是向往著自己有美好、自由的生活舷礼。...
    楊藝萱閱讀 792評論 0 0