使用dispatch_group實現並封裝分組並發網絡請求


在實際開發中我們通常會遇到這樣一種需求:某個頁面加載時通過網絡請求獲得相應的數據,再做某些操作。有時候加載的內容需要通過好幾個請求的數據組合而成,比如有兩個請求A和B,我們通常為了省事,會將B請求放在A請求成功的回調中發起,在B的成功回調中將數據組合起來,這樣做有明顯的問題:

1.請求如果多了,需要寫許多嵌套的請求

2.如果在除了最后一個請求前的某個請求失敗了,就不會執行后面的請求,數據無法加載

3.請求變成同步的,這是最大的問題,在網絡差的情況下,如果有n個請求,意味着用戶要等待n倍於並發請求的時間才能看到內容

 

同步請求這么low的方式當然是不可接受的,所以我們要並發這些請求,在所有請求都執行完成功回調后,再做加載內容或其他操作,考慮再三,選擇用GCD的dispatch_group。

dispatch_group通常有兩種用法,一種是

dispatch_group_async(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)

創建一個dispatch_group_t, 將並發的操作放在block中,在

dispatch_group_notify(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)

的block中執行多組block執行完畢后的操作,對於網絡請求來說,在請求發出時他就算執行完畢了,並不會等待回調,所以不滿足我們的需求。

所以采用另一種用法:

使用dispatch_group_enter和dispatch_group_leave,這種方式使用更為靈活,enter和leave必須配合使用,有幾次enter就要有幾次leave,否則group會一直存在。當所有enter的block都leave后,會執行dispatch_group_notify的block。

我們當然可以在網絡請求前enter,在執行完每個請求的成功回調后leave,再在notify中執行內容加載,這樣看來問題就解決了,但還是有點小小不爽,每次發起組請求我都得創建group,寫一堆的enter和leave,既麻煩也不利於復用,很自然我們想到把他封裝一下,最好能做到將一個網絡請求加到組里,而不用修改原先的網絡請求代碼,就像這樣:

[[MDXuexiBaoAPI sharedInstance] postForGroup:^{
        request1 success:^(id responseObject) {
            
        } failure:^(NSError *error) {
            
        }];
        
        request2 success:^(id responseObject) {

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

如果我想做到這種效果,肯定要到網絡單例層去做些修改,但我又不想改變現有的底層請求方法,所以我采用了

method_exchangeImplementations(<#Method m1#>, <#Method m2#>) 這個函數,基於現有的底層請求方法,實現一套組的請求方法,替換掉原先的方法,在組請求都發送完畢后,再換回原先的方法。

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

1.我做了替換后,正常的非組網絡請求也會走替換后的方法,但我不需要他走替換后的方法。

2.假如我同時發起了多個組請求,組和組之間要如何區分,不同的組是不應該相互影響的。

 

一開始我考慮給請求一個mark,標記他是屬於哪個group的,但這需要你已經把請求封裝成了一個對象,如果你的項目和我的一樣,只是執行一個方法,是不好給他加標記的。

在一陣頭腦風暴后,我決定用隊列來區分每個gorup。

具體做法就是創建每個group時,開啟一個隊列,給隊列動態添加group屬性,一個隊列對應一個group。在隊列中替換方法,發起組里的請求,再替換回原先的方法,這樣在replaced method里只需要拿到當前的隊列,就可以拿到group,如果group是nil,說明是正常的非組請求,執行original method;如果group不是nil,根據group來enter和leave,每個group也能區分開。

創建group時,給group添加了一個errorArray,用來記錄組里請求的error,只要errorArray不為空,就會走組失敗的block。

下面上代碼:

 

typedef void(^BlockAction)();
typedef void(^GroupResponseFailure)(NSArray * errorArray);
static char groupErrorKey;
static char queueGroupKey;
- (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) {
        // 執行original method
        [self sendPOSTRequestInGroup:strURL withData:data paramForm:paramForm withTimeout:timeout showAlert:show success:success failure:failure];
        return;
    }
    
    dispatch_group_enter(group);
    // 執行original method
    [self sendPOSTRequestInGroup:strURL withData:data paramForm:paramForm withTimeout:timeout showAlert:show success:^(id responseObject) {
        success(responseObject);
        dispatch_group_leave(group);
    } failure:^(NSError *error) {
        NSMutableArray *arrayM = objc_getAssociatedObject(group, &groupErrorKey);
        [arrayM addObject:error];
        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);
        requests();
        // 發出請求后就可以替換回original method,不必等待回調,盡量減小替換的時間窗口
        method_exchangeImplementations(originalPost, groupPost);
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSMutableArray *arrayM = objc_getAssociatedObject(group, &groupErrorKey);
            if (arrayM.count > 0) {
                if (failure) {
                    failure(arrayM.copy);
                }
            } else if(success) {
                success();
            }
            
        });
    }];
}

 

- (void)sendPOSTRequestInGroup:(NSString *)strURL withData:(NSDictionary *)data paramForm:(ParamForm)paramForm withTimeout:(NSTimeInterval)timeout showAlert:(BOOL)show success:(BlockResponse)success failure:(BlockResponseFailure)failure
替換網絡工具層的底層post請求

sendPOSTRequest:withData:paramForm:withTimeout:showAlert:success:failure:

這樣在 

- (void)sendGroupPostRequest:(BlockAction)requests success:(BlockAction)success failure:(GroupResponseFailure)failure
requests block中,把網絡請求扔進去,單個請求本身的success和failure都能執行,success執行組成功的代碼,failure中可以拿到每個請求的error,作相應處理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM